<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Juan Rodriguez Donado - sjdonado</title><description>Software Engineer from Barranquilla, currently based in Berlin. I work across web, mobile, distributed systems, databases, and offline-first architectures.</description><link>https://sjdonado.com/</link><language>en-us</language><item><title>Self-Hosted Password Manager with Dokku</title><link>https://sjdonado.com/posts/2023-04-19-self-hosted-password-manager-with-dokku/</link><guid isPermaLink="true">https://sjdonado.com/posts/2023-04-19-self-hosted-password-manager-with-dokku/</guid><description>Details the process of setting up a self-hosted password manager using Dokku and Vaultwarden.</description><pubDate>Wed, 19 Apr 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;why&quot;&gt;Why?&lt;/h2&gt;
&lt;p&gt;I relied on 1Password for the last few years, it is robust, and very user-friendly. However, these types of services are prime targets for attacks, one popular example is the &lt;a href=&quot;https://www.forbes.com/sites/daveywinder/2023/03/03/why-you-should-stop-using-lastpass-after-new-hack-method-update/?sh=70d57e3d28fc&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;Lastpass&lt;/a&gt; case. It is definitely not cool to have your sensitive info spread in another data breach. Also, privacy is precious nowadays, whoever has the data, has the power.&lt;/p&gt;
&lt;p&gt;While exploring Open Source alternatives, I found &lt;a href=&quot;https://github.com/dani-garcia/vaultwarden&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;vaultwarden&lt;/a&gt;, an unofficial Bitwarden-compatible server written in Rust.&lt;/p&gt;
&lt;p&gt;Migrating data manually is a tedious process, so I first checked how it would be like with Bitwarden. Surprise, surprise, they have it all! You don’t have to do it by hand, it already supports the most popular password managers.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Web - Import Data Page&lt;/em&gt;
&lt;img src=&quot;../images/k7tn5l91lm52b0qkg8fh.png&quot; alt=&quot;Bitwarden Import Data Page&quot;&gt;&lt;/p&gt;
&lt;p&gt;PS: It was a relief to discover a well-done user interface, here’s a spoiler:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;macOS app&lt;/em&gt;
&lt;img src=&quot;../images/hgjgb2boxexkqrwxhfwr.png&quot; alt=&quot;macos App&quot;&gt;&lt;/p&gt;
&lt;p&gt;To make it cost effective, I decided to use my current Dokku setup, hosted on Google Cloud and powered by Ubuntu 20.04. If you haven’t heard of Dokku, this is your chance to try it out &lt;a href=&quot;https://dokku.com/docs/getting-started/installation/&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;https://dokku.com/docs/getting-started/installation/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So, having in mind the advantages and the infra required, here’s how I did it.&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before we get started, check that you already have the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A server with Dokku installed.&lt;/li&gt;
&lt;li&gt;A domain name (letsencrypt will check it).&lt;/li&gt;
&lt;li&gt;Local machine with Docker, Git and any SSH client.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;step-by-step&quot;&gt;Step by Step&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Create Dokku App&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Set your app name and link to the domain (the Dokku Proxy plugin is built in since version 0.5.0)&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;dokku&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; apps:create&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; bitwarden&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# example: dokku domains:set bitwarden mypasswords.sjdonado.de&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;dokku&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; domains:set&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; bitwarden&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; yourdomain.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. Set up TLS certificates&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The Let’s Encrypt plugin automates the generation and renewal of our certificates&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# plugin installation requires root, hence the user change&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; dokku&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; plugin:install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://github.com/dokku/dokku-letsencrypt.git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;dokku&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; letsencrypt:enable&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; bitwarden&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# enable the automatic renewal of certificates&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;dokku&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; letsencrypt:cron-job&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --add&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# replace proxy port, default: 5000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;dokku&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; config:set&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; bitwarden&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; DOKKU_PROXY_PORT_MAP=&quot;http:80:80 https:443:80&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. Set up database&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We’ll use the PostgreSQL plugin&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# setup plugin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; dokku&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; plugin:install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://github.com/dokku/dokku-postgres.git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# create a new database for our app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;dokku&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; postgres:create&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; bitwarden&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# link the postgres service to the app, DATABASE_URL will be attached to the ENV variables automatically.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;dokku&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; postgres:link&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; bitwarden&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; bitwarden&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4. Set up persistent storage&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We can use the built-in Dokku storage plugin&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;dokku&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; storage:ensure-directory&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; bitwarden&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;dokku&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; storage:mount&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; bitwarden&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /var/lib/dokku/data/storage/bitwarden:/data&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;5. Set up ENV variables&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;By default, Bitwarden is open to public signups, although we can restrict it to invitation-only by setting &lt;code&gt;SIGNUPS_ALLOWED&lt;/code&gt; to false and providing a valid &lt;code&gt;ADMIN_TOKEN&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We’re gonna use &lt;code&gt;argon2&lt;/code&gt; to generate a PHC string for the &lt;code&gt;ADMIN_TOKEN&lt;/code&gt; ENV variable&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# skip this line if you are a linux user&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --rm&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -it&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ubuntu&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; apt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; update&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;apt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; openssl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; argon2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; echo&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;MySecretPassword&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; argon2&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;$(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;openssl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; rand &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;-base64&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 32&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;)&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -e&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -id&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -k&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 65540&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -t&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 3&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# output: $argon2id$v=19$m=65540,t=3,p=4$bXBGMENBZUVzT3VUSFErTzQzK25Jck1BN2Z0amFuWjdSdVlIQVZqYzAzYz0$T9m73OdD2mz9+aJKLuOAdbvoARdaKxtOZ+jZcSL9/N0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the &lt;code&gt;SMTP_&lt;/code&gt; variables, feel free to use your own SMTP server or comercial solutions with free-tier such as &lt;a href=&quot;https://sendgrid.com/&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;sendgrid&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;dokku&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; config:set&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; bitwarden&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  DOMAIN=https://mypasswords.sjdonado.de&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  SIGNUPS_ALLOWED=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  ADMIN_TOKEN=&apos;$argon2id...&apos;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  SMTP_HOST=smtp.sjdonado.de&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  SMTP_FROM=vaultwarden@sjdonado.de&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  SMTP_PORT=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;587&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  SMTP_SECURITY=starttls&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  SMTP_USERNAME=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  SMTP_PASSWORD=&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;6. Create the Dockerfile&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let’s create a new folder for the project, the Dockerfile and initialise git&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mkdir&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; yourfolder&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; yourfolder&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; init&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;touch&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Dockerfile&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;yourfolder/Dockerfile&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;dockerfile&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; vaultwarden/server:latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;EXPOSE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; 80&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ENV&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; DB=postgresql&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;7. Deploy&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We are almost there! Before to push the changes, we have to set the dokku remote url&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; remote&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; dokku-bitwarden&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; dokku@yourdomain.com:bitwarden&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;git add -A &amp;#x26;&amp;#x26; git commit -m &quot;My bitwarden Dockerfile&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;git push dokku master&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# output:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;git push using:  dokku master&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Enumerating objects: 6, done.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Counting objects: 100% (6/6), done.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Delta compression using up to 8 threads&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Compressing objects: 100% (3/3), done.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Writing objects: 100% (6/6), 551 bytes | 551.00 KiB/s, done.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Total 6 (delta 0), reused 0 (delta 0), pack-reused 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-----&gt; Cleaning up...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-----&gt; Building bitwarden from Dockerfile&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;remote: #1 [internal] load build definition from Dockerfile&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;remote: #1 transferring dockerfile: 65B done&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;remote: #1 DONE 0.0s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;remote:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;remote: #2 [internal] load .dockerignore&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;remote: #2 transferring context: 2B done&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;remote: #2 DONE 0.1s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;8. That’s it&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now you can access the admin panel via &lt;a href=&quot;https://yourdomain.com/admin&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;https://yourdomain.com/admin&lt;/a&gt; with your &lt;code&gt;MySecretPassword&lt;/code&gt; (from step 5) and send invitations by email 😎.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/h421y9wu4ap1kgd8m8ta.png&quot; alt=&quot;Admin Portal&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; Be sure to add your domain when installing the official Bitwarden apps/extensions.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Chrome Extension&lt;/em&gt;
&lt;img src=&quot;../images/3txh5xhfjsk82f8ocs8f.png&quot; alt=&quot;Chrome Extension example&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;We’ve explored the process of setting up a self-hosted password manager using Dokku. This can be easily replicated on any IaaS provider.&lt;/p&gt;
&lt;p&gt;Now, you can have peace of mind knowing that your passwords are securely stored and easily accessible whenever and wherever you need them.&lt;/p&gt;
&lt;p&gt;Source code available on &lt;a href=&quot;https://github.com/sjdonado/dokku-self-hosted-services&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Happy hacking!&lt;/p&gt;</content:encoded></item><item><title>HTMX with Bun: A Real World App</title><link>https://sjdonado.com/posts/2024-03-05-htmx-with-bun-a-real-world-app/</link><guid isPermaLink="true">https://sjdonado.com/posts/2024-03-05-htmx-with-bun-a-real-world-app/</guid><description>Build a custom &apos;useFetcherForm&apos; hook to easily handle fetcher requests.</description><pubDate>Tue, 05 Mar 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Let’s build a real-world app with HTMX + Tailwind CSS + Bun. If you are already familiar with these tools, feel free to skip the context part.&lt;/p&gt;
&lt;h2 id=&quot;context&quot;&gt;Context&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;HTMX, HTMZ, HTMY…&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;All we need to achieve decent smooth reactivity is already out there, built-in by the major browsers. As an example, here is one interesting front-end framework:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;html&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;iframe&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  hidden&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;htmz&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; onload&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setTimeout&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;(()&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;document&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;querySelector&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;contentWindow&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;location&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;hash&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;)?.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;replaceWith&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;contentDocument&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;childNodes&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;))&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;iframe&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it, just an iframe, try it out yourself on &lt;a href=&quot;https://leanrada.com/htmz/&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;https://leanrada.com/htmz/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The philosophy behind this is the same as HTMX and the same one that drives this post: Don’t reinvent rendering, just use HTML.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/ddj62wppkbdxcb9o3ose.png&quot; alt=&quot;HTMX meme&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The blazingly fast JS server&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;With HTMX in mind, the next step is to get up and running a a simple web server.&lt;/p&gt;
&lt;p&gt;Bun has gained a lot of popularity in the last few months. It is fast, and there is a framework called &lt;a href=&quot;https://elysiajs.com/&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;ElysiaJS&lt;/a&gt; that resembles Express.js and promises to be 22 times faster than Express.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/8kqqyohtrk1p14x1pnsx.png&quot; alt=&quot;ElysiaJS Benchmark&quot;&gt;&lt;/p&gt;
&lt;p&gt;Additionally, Bun understands TypeScript and JSX out of the box, so we can structure our app with components, server-side render them, and rely on HTMX for client-side actions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tailwind CSS&lt;/strong&gt;
Unlike traditional CSS frameworks that come with predefined components, Tailwind CSS emphasizes atomic classes that represent individual CSS properties. The bundle size could be lighter in comparison to other tools, but the strength here, IMHO, is rapid prototyping.&lt;/p&gt;
&lt;h2 id=&quot;a-real-world-app&quot;&gt;A Real World App&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;../images/tyyrd88dd2m71xlehd4e.png&quot; alt=&quot;I Don&amp;#x27;t Have Spotify Web App&quot;&gt;&lt;/p&gt;
&lt;p&gt;The problem: I’m an Apple Music user. Someone shares a Spotify link with me, and I don’t have a way to know what’s inside or listen to it.&lt;/p&gt;
&lt;p&gt;The solution: I Don’t Have Spotify scrapes the links (songs/albums/artists etc) from all your favorite streaming services, and it even provides a listening preview if you just want a quick look.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The approach&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Given a Spotify link, we need to extract its metadata: Fetch request + HTML parsing.&lt;/li&gt;
&lt;li&gt;Given the metadata, we need to search for the resource on each supported streaming service: Multiple API/HTML requests + parsing (adapters).&lt;/li&gt;
&lt;li&gt;Given the results of (1) and (2), we need to render and return the data to the client.&lt;/li&gt;
&lt;li&gt;Additionally, we list a JSON endpoint for a cool Raycast extension.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There are two major design decisions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The folder structure / separation of concerns&lt;/li&gt;
&lt;li&gt;Tooling + build process, how to live reload the JSX components?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the first one, since we have multiple streaming services, we’ll inevitably encounter shared logic between them. Drawing inspiration from the adapter pattern but without the boilerplate, I organized the adapters folder with pure functions. These functions all receive the same arguments: &lt;code&gt;query: string&lt;/code&gt; and &lt;code&gt;metadata: SpotifyMetadata&lt;/code&gt;, and they return a &lt;code&gt;SpotifyContentLink&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The adapters are called by the &lt;code&gt;spotifySearch&lt;/code&gt; function in the search service, which is also responsible for caching and updating the statistics. Why caching? In short: these calls are quite time-consuming, and we can easily hit a rate limit if we send many of them at the same time.&lt;/p&gt;
&lt;p&gt;For the second aspect, running Bun is very straightforward: &lt;code&gt;bun run --watch www/bin.ts&lt;/code&gt;. However, as a real-world app, we need some JavaScript on the client side, apart from sending AJAX requests. There is an audio player that has to be rendered on the client side (in order to append listeners to the DOM). Additionally, we would like to access the Clipboard API to improve the user experience when searching.&lt;/p&gt;
&lt;p&gt;To bundle and minify the required JavaScript, I used Vite with the &lt;code&gt;rollup-plugin-copy&lt;/code&gt; plugin:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; path &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;path&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { defineConfig } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;vite&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; copy &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;rollup-plugin-copy&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; default&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; defineConfig&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  plugins: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    copy&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      targets: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          src: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;dist/*&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          dest: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;public&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          copyOnce: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      hook: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;writeBundle&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  resolve: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    alias: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &apos;~/config/constants&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: path.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(__dirname, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;./src/config/constants.ts&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  build: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    outDir: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;./dist&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    target: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;esnext&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    rollupOptions: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      input: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &apos;assets/js/audio-preview&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;./src/views/js/audio-preview.js&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &apos;assets/js/search-bar&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;./src/views/js/search-bar.js&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &apos;assets/css/index&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;./src/views/css/index.css&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      output: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        entryFileNames: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;[name].min.js&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        chunkFileNames: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`[name].min.js`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        assetFileNames: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`[name].min.[ext]`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Showcase&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The search bar component (where HTMX shines)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; SearchBar&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x3C;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;form&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        id&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;search-form&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        hx&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/search&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        hx&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;#search-results&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        hx&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;swap&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;innerHTML&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        hx&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;indicator&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;#loading-indicator&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        hx&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;timeout&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:24000&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        class&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;flex w-full max-w-3xl items-center justify-center&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;label for&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;song-link&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;sr-only&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;          Search&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;label&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;input&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          type&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          id&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;song-link&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;spotifyLink&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          class&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;flex-1 rounded-lg border bg-white p-2.5 text-sm font-normal text-black placeholder:text-gray-400 lg:text-base&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          placeholder&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://open.spotify.com/track/7A8MwSsu9efJXP6xvZfRN3?si=d4f1e2eb324c43df&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          pattern&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{SPOTIFY_LINK_REGEX.source}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;button&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          type&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;submit&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          class&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;ml-2 rounded-lg border border-green-500 bg-green-500 p-2.5 text-sm font-medium text-white focus:outline-none focus:ring-1 focus:ring-white&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;          &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;i class&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;fas fa-search p-1 text-black&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;          &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;span class&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;sr-only&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Search&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;form&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;div class&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;my-4&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;div id&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;search-results&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x3C;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The web server&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { Elysia } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;elysia&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { html } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;@elysiajs/html&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { staticPlugin } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;@elysiajs/static&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { logger } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;./utils/logger&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { apiRouter } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;./routes/api&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { pageRouter } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;./routes/page&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; app&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Elysia&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    staticPlugin&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      prefix: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      headers: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &apos;Cache-Control&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;public, max-age=86400&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;beforeHandle&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ({ &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    logger.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      `${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;method&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;} ${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;} - ${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;headers&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user-agent&apos;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(apiRouter)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(pageRouter);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Testing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In our case, testing involves mocks and more mocks. They run on push changes to master, thanks to GitHub Actions:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/x50fehumi35q0kcomxdy.png&quot; alt=&quot;Testing output from Github Actions&quot;&gt;&lt;/p&gt;
&lt;p&gt;The setup relies on &lt;code&gt;bun:test&lt;/code&gt; and &lt;code&gt;AxiosMockAdapter&lt;/code&gt;. It is separated into integration and unit tests, and the requests are injected using two helpers:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; JSONRequest&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;endpoint&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; object&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Request&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(endpoint, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    method: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;POST&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    headers: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &apos;Content-Type&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    body: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(body),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; formDataRequest&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;endpoint&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; object&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; formData&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; FormData&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  Object.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;entries&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(body).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(([&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    formData.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;append&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(key, value);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Request&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(endpoint, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    method: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;POST&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    body: formData,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Incorporating e2e shouldn’t be complex. An example of running the dev server with Playwright:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  webServer: {    command: &apos;bun run dev&apos;,    port: 3333,    reuseExistingServer: !process.env.CI,  }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The source code is available on GitHub: &lt;a href=&quot;https://github.com/sjdonado/idonthavespotify&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;https://github.com/sjdonado/idonthavespotify&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The app is up and running thanks to Dokku: &lt;a href=&quot;https://idonthavespotify.sjdonado.com&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;https://idonthavespotify.sjdonado.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The Raycast extension will be addressed in a future post, stay tuned: &lt;a href=&quot;https://www.raycast.com/sjdonado/idonthavespotify&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;https://www.raycast.com/sjdonado/idonthavespotify&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Fast, Intuitive, Smart: Restaurant search engine with Cloudflare AI</title><link>https://sjdonado.com/posts/2024-04-13-fast-intuitive-smart-restaurant-search-engine-with-cloudflare-ai/</link><guid isPermaLink="true">https://sjdonado.com/posts/2024-04-13-fast-intuitive-smart-restaurant-search-engine-with-cloudflare-ai/</guid><description>Discover &apos;Cule filo&apos; - An AI-Powered Restaurant Finder.</description><pubDate>Sat, 13 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import culefiloSearch from ’../images/kjp6ltwap8ithxp8b97w.gif?url’;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This project was submitted to the &lt;a href=&quot;https://dev.to/devteam/join-us-for-the-cloudflare-ai-challenge-3000-in-prizes-5f99&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;Cloudflare AI Challenge&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-inspiration-behind-cule-filo&quot;&gt;The Inspiration Behind “Cule filo”&lt;/h3&gt;
&lt;p&gt;Imagine you’re craving your favorite meal—a local specialty or a unique dish you tried once and can’t forget. Now, imagine finding the best spot for that meal near you, simply by typing the dish name and your location. That’s where &lt;strong&gt;“Cule filo”&lt;/strong&gt; comes in, our AI-powered restaurant search engine that guides you to the top 3 restaurants serving exactly what you’re in the mood for. Built with powerful AI models and designed for simplicity, this free app is your smart companion to uncover culinary gems in your area.&lt;/p&gt;
&lt;img src=&quot;{culefiloSearch}&quot; alt=&quot;Searching on culefilo&quot;&gt;
&lt;h3 id=&quot;our-development-journey&quot;&gt;Our Development Journey&lt;/h3&gt;
&lt;p&gt;Building “Cule filo” was an exhilarating journey that started with a brainstorm on how to leverage Cloudflare’s AI tools to bring real value to users. After exploring various ideas, we zeroed in on the concept of a restaurant search engine that uses AI to make dining discovery intuitive and exciting.&lt;/p&gt;
&lt;p&gt;To make it happen, we brought together multiple AI models, each serving a distinct role in shaping a smooth user experience:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Suggestions &amp;#x26; Thumbnails&lt;/strong&gt;: We used the &lt;code&gt;llama-2-13b-chat-awq&lt;/code&gt; model to suggest alternative restaurants when fewer than 3 results matched the search, ensuring users always find relevant options. The same model selects thumbnails for the results, making them visually appealing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Summarized Descriptions&lt;/strong&gt;: For quick overviews of each restaurant, we turned to the &lt;code&gt;bart-large-cnn&lt;/code&gt; model, which condenses user reviews into concise descriptions that capture each place’s unique qualities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Image-to-Text Descriptions&lt;/strong&gt;: Lastly, we used the &lt;code&gt;uform-gen2-qwen-500m&lt;/code&gt; model to analyze restaurant photos and generate text descriptions, giving users a sense of the restaurant ambiance before they even step inside.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Throughout development, we prioritized creating an interface that feels both responsive and intuitive. Real-time search logs keep users engaged, and a search history feature allows easy access to previous explorations.&lt;/p&gt;
&lt;h3 id=&quot;ai-models-in-action&quot;&gt;AI Models in Action&lt;/h3&gt;
&lt;p&gt;Our project is particularly innovative in its use of &lt;strong&gt;multiple AI models per task&lt;/strong&gt; and the integration of &lt;strong&gt;three distinct AI task types&lt;/strong&gt;, making it a standout submission in the challenge:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Multiple Models per Task&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;llama-2-13b-chat-awq&lt;/code&gt; model is used both for generating suggestions when the initial search yields limited results and for selecting suitable thumbnails.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;bart-large-cnn&lt;/code&gt; model generates summaries of user reviews, giving a clear snapshot of what to expect at each restaurant.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;uform-gen2-qwen-500m&lt;/code&gt; model converts restaurant images into textual descriptions, adding depth and context to the search results.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Triple Task Types&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Text Generation&lt;/strong&gt;: The &lt;code&gt;llama-2-13b-chat-awq&lt;/code&gt; model generates recommendations and captions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Text Summarization&lt;/strong&gt;: The &lt;code&gt;bart-large-cnn&lt;/code&gt; model condenses user reviews into summaries.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Image-to-Text&lt;/strong&gt;: The &lt;code&gt;uform-gen2-qwen-500m&lt;/code&gt; model creates descriptions based on restaurant images.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;to-wrap-up&quot;&gt;To wrap up&lt;/h3&gt;
&lt;p&gt;This project was a true team effort, and I couldn’t have done it without the creativity and support of my awesome friends and teammates: &lt;a href=&quot;https://github.com/gjhernandez&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;gjhernandez&lt;/a&gt; and &lt;a href=&quot;https://github.com/krthr&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;krthr&lt;/a&gt;. Building this together was a blast, and we’re proud of what we’ve accomplished.&lt;/p&gt;
&lt;p&gt;If you’re curious about the inner workings or just want to poke around the code, you’re more than welcome to check out the repository: &lt;a href=&quot;https://github.com/sjdonado/culefilo&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;https://github.com/sjdonado/culefilo&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Validated forms with useFetcher in Remix</title><link>https://sjdonado.com/posts/2024-07-10-validated-forms-with-usefetcher-in-remix/</link><guid isPermaLink="true">https://sjdonado.com/posts/2024-07-10-validated-forms-with-usefetcher-in-remix/</guid><description>Build a custom &apos;useFetcherForm&apos; hook to easily handle fetcher requests.</description><pubDate>Wed, 10 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Interacting with the server without using &lt;code&gt;window.navigation&lt;/code&gt; significantly improves the user experience. E.g: Login forms within a dialog box or modal, optimistic UI forms or submitting multiple forms within a complex view.&lt;/p&gt;
&lt;p&gt;If you are not familiar with Remix or the useFetcher hook, please refer to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://remix.run/docs/en/main/hooks/use-fetcher&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;https://remix.run/docs/en/main/hooks/use-fetcher&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://remix.run/docs/en/main/discussion/form-vs-fetcher&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;https://remix.run/docs/en/main/discussion/form-vs-fetcher&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-fetcherform-provider&quot;&gt;The FetcherForm Provider&lt;/h2&gt;
&lt;p&gt;The Remix &lt;code&gt;fetcher&lt;/code&gt; object contains three primary attributes: &lt;code&gt;fetcher.state&lt;/code&gt;, &lt;code&gt;fetcher.data&lt;/code&gt; and the method &lt;code&gt;fetcher.submit&lt;/code&gt;. To interact with them we will use &lt;code&gt;React.useEffect&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This post will show how to create a provider that manages the state of a fetcher submitted form, along with a minimal custom hook:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;onChange&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;submitForm&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;isSubmitted&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; useFetcherForm&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s break down the FetcherFormProvider props one by one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1) onChange&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;External inputs will notify through this method to change the internal state of the provider, it receives a &lt;code&gt;FormData&lt;/code&gt; argument:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; FetcherFormProvider&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  action&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  method&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  children&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  action&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  method&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; SubmitOptions&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;method&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  children&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; React&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ReactNode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;formData&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;setFormData&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; useState&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;FormData&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  [&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;FetcherFormContext.Provider&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      value&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;formData&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; FormData&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;          setFormData&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(formData);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        [&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ]}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      {children}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;/FetcherFormContext.Provider&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2) submitForm&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Since the &lt;code&gt;formData&lt;/code&gt; was already captured by the &lt;code&gt;onChange&lt;/code&gt; method, the request can be send by calling &lt;code&gt;fetcher.submit&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; FetcherFormProvider&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  action&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  method&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  children&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  action&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  method&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; SubmitOptions&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;method&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  children&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; React&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ReactNode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; fetcher&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; useFetcher&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;formData&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;setFormData&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; useState&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;FormData&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  [&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;FetcherFormContext.Provider&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      value&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;formData&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; FormData&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;          setFormData&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(formData);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;          if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (formData) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            fetcher.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;submit&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(formData, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;              method,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;              action,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        [&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      ]}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      {children}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;/FetcherFormContext.Provider&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3) isSubmitted&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Submitting the form is an asynchronous operation. There is a separate variable to listen to the form submitted event: &lt;code&gt;isSubmitted&lt;/code&gt;. This is helpful in the following cases:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;To close the Dialog or Modal when the form is successfully submitted.&lt;/li&gt;
&lt;li&gt;To check from outside that the form was submitted and/or the request returned an OK status.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;diff&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;export default function FetcherFormProvider({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  action,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  method,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  children,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  action: string;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  method: SubmitOptions[&apos;method&apos;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  children: React.ReactNode;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  const fetcher = useFetcher();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt; const [isSubmitted, setIsSubmitted] = useState(false);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  [...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  const [formData, setFormData] = useState&amp;#x3C;FormData&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  useEffect(() =&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    const response = fetcher.data as { error: string } | undefined;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;   if (isSubmitted || error) return;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    if (fetcher.state === &apos;loading&apos; &amp;#x26;&amp;#x26; response) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      [...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;     setIsSubmitted(true);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }, [fetcher, action, formData, isSubmitted, error]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  return (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;FetcherFormContext.Provider&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      value={[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        (formData: FormData) =&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          setFormData(formData);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        () =&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          if (formData) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            fetcher.submit(formData, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;              method,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;              action,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;       isSubmitted,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        [...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      ]}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      {children}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;/FetcherFormContext.Provider&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4) error&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;One drawback of using Remix &lt;code&gt;useFetcher&lt;/code&gt; is the lack of a straightforward error handling method. There are proposals in progress to provide a more streamlined error handling approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/remix-run/remix/discussions/4645&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;https://github.com/remix-run/remix/discussions/4645&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/remix-run/react-router/discussions/10013&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;https://github.com/remix-run/react-router/discussions/10013&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As a workaround we can rely on &lt;code&gt;fetcher.state&lt;/code&gt; to check if the request is complete and get the message with &lt;code&gt;fetcher.data&lt;/code&gt; by defining a common structure between the client and server actions.&lt;/p&gt;
&lt;p&gt;The highlights&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;const response = fetcher.data as { error: string } | undefined;&lt;/code&gt; -&gt; defines the JSON response structure to be received form the server &lt;code&gt;return json({ error: new Error() }, { status: 400 } );&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;if (fetcher.state === &apos;loading&apos; &amp;#x26;&amp;#x26; response) {&lt;/code&gt; -&gt; checks if the request is completed and a response is available.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;diff&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;export default function FetcherFormProvider({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  action,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  method,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  children,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  action: string;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  method: SubmitOptions[&apos;method&apos;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  children: React.ReactNode;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  const fetcher = useFetcher();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  const [isSubmitted, setIsSubmitted] = useState(false);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt; const [error, setError] = useState&amp;#x3C;string&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  const [formData, setFormData] = useState&amp;#x3C;FormData&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  useEffect(() =&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    const response = fetcher.data as { error: string } | undefined;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    if (isSubmitted || error) return;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    if (fetcher.state === &apos;loading&apos; &amp;#x26;&amp;#x26; response) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;     if (response.error) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;       setError(response.error);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;       return;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;     }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      setIsSubmitted(true);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }, [fetcher, action, formData, isSubmitted, error]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  return (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;FetcherFormContext.Provider&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      value={[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        (formData: FormData) =&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          setFormData(formData);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;         setError(undefined);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        () =&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          if (formData) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            fetcher.submit(formData, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;              method,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;              action,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        isSubmitted,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;       error,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      ]}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      {children}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;/FetcherFormContext.Provider&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Optionally, the error state management can be sent as a callback. This can be received in the &lt;code&gt;onSubmit&lt;/code&gt; function and registered as state. For example, using a &lt;code&gt;registeredCallback&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;diff&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;-&lt;/span&gt; if (response.error) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;-&lt;/span&gt; setError(response.error);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;-&lt;/span&gt; return;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;-&lt;/span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt; registeredCallback?.(response.error);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;the-usefetcherform-hook&quot;&gt;The useFetcherForm hook&lt;/h2&gt;
&lt;p&gt;The values sent to &lt;code&gt;FetcherFormContext.Provider&lt;/code&gt; are defined in the &lt;code&gt;FetcherFormContext&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; FetcherFormContext&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; createContext&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  [(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;formData&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; FormData&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; void&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;callback&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; void&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; void&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, boolean, string&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the hook exposes them in an object structure:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; useFetcherForm&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;onChange&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;submitForm&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;isSubmitted&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; useContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(FetcherFormContext);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    onChange,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    submitForm,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    isSubmitted,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    error,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An example of how it can be defined:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AssignmentUpdateStatusDialogButton&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  assignmentId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  status&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  assignmentId&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  status&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AssignmentStatus&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;isAttached&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;setIsAttached&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; useState&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; dialog&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; useRef&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HTMLDialogElement&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  useEffect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (isAttached) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      dialog.current?.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;showModal&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }, [isAttached, dialog]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ClientOnly&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      {() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        &amp;#x3C;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          {&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;isAttached&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x26;&amp;#x26;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            createPortal&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;              &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;FetcherFormProvider&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                action&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`/assignments/${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;assignmentId&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}/status`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                method&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;post&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;              &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;AssignmentUpdateStatusDialog&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                  ref&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{dialog}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                  [...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                  setIsAttached&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{setIsAttached}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;              &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;FetcherFormProvider&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;              document&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;body&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            )}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;          &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;button&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            type&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;button&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            className&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;cursor-pointer leading-none&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            onClick&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{() =&gt; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;setIsAttached&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;          &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;AssignmentStatusBadge status&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{status} &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;          &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        &amp;#x3C;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      )}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ClientOnly&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo&lt;/h2&gt;
&lt;p&gt;Real-world example available on &lt;a href=&quot;https://github.com/sjdonado/remix-dashboard/blob/da9445646392626cea065442f7758230b3d8d1fa/app/components/dialog/AssignmentUpdateStatusDialog.tsx#L32C56-L32C70&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Building a Fast and Compact SQLite Cache Store</title><link>https://sjdonado.com/posts/2024-07-24-building-a-fast-and-compact-sqlite-cache-store/</link><guid isPermaLink="true">https://sjdonado.com/posts/2024-07-24-building-a-fast-and-compact-sqlite-cache-store/</guid><description>bun:sqlite combined with Msgpackr serialization, an efficient solution for building fast and compact cache stores.</description><pubDate>Wed, 24 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When working on applications, caching is crucial for enhancing performance by reducing the need for repeated database fetches. Among the various SQLite libraries available, Bun’s native integration is optimized for performance and takes advantage of its non-blocking I/O capabilities.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;bun:sqlite&lt;/code&gt; module is roughly 3-6x faster than &lt;code&gt;better-sqlite3&lt;/code&gt; and 8-9x faster than &lt;code&gt;deno.land/x/sqlite&lt;/code&gt; for read queries. Each driver was benchmarked against the Northwind Traders dataset. View and run the benchmark source. &lt;a href=&quot;https://bun.sh/docs/api/sqlite&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In addition to efficient caching, serializing JavaScript objects can be slow when using JSON. This is why it makes sense to opt for efficient binary encoding alternatives like Msgpackr or CBOR. These formats are faster to parse, support complex data types, require less CPU usage, and store data more compactly, further enhancing overall application performance.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/ux7b8g474xe68kv37yum.png&quot; alt=&quot;Encoding/Decoding performance&quot;&gt;&lt;/p&gt;
&lt;p&gt;With this in mind, let’s explore how to build a cache manager using &lt;code&gt;bun:sqlite&lt;/code&gt; along with efficient binary encoding.&lt;/p&gt;
&lt;h2 id=&quot;a-cache-manager-store&quot;&gt;A &lt;a href=&quot;https://github.com/jaredwray/cache-manager&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;cache-manager&lt;/a&gt; Store&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;cache-manager&lt;/code&gt; provides a straightforward and intuitive API for caching, abstracting away the complexity of managing different cache stores and their configurations. With support for multiple stores, built-in expiration and TTL management, and robust error handling and fallback mechanisms, it ensures data integrity and freshness.&lt;/p&gt;
&lt;p&gt;Additionally, &lt;code&gt;cache-manager&lt;/code&gt; is highly customizable and extensible, allowing you to create custom cache stores tailored to your needs. This flexibility means you can set up and use caching with minimal code, allowing you to focus on your application’s core logic.&lt;/p&gt;
&lt;p&gt;The required interface to fulfill as a cache-manager store is as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Store&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; T&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ttl&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Milliseconds&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    del&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    reset&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    mset&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;arguments_&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Array&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;unknown&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ttl&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Milliseconds&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    mget&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;arguments_&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[])&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;unknown&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[]&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    mdel&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;arguments_&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[])&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    keys&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;pattern&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[]&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    ttl&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See more &lt;a href=&quot;https://github.com/jaredwray/cache-manager/blob/main/packages/cache-manager/src/caching.ts#L21C1-L32C1&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-queries&quot;&gt;The Queries&lt;/h2&gt;
&lt;p&gt;Configuring SQLite for Optimal Performance&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PRAGMA main.synchronous = NORMAL;&lt;/code&gt;: Ensures that SQLite writes are fast while still maintaining a reasonable level of data safety. It does not guarantee as much durability as FULL, but it is sufficient for many use cases.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PRAGMA main.journal_mode = WAL2;&lt;/code&gt;: Improves concurrency by allowing readers to access the database while a write operation is ongoing.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PRAGMA main.auto_vacuum = INCREMENTAL;&lt;/code&gt;: Allows SQLite to reclaim unused space incrementally, rather than all at once.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;1. Creating the Cache Table&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IF&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; EXISTS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;} (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TEXT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; PRIMARY KEY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    val BLOB,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    created_at &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INTEGER&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    expire_at &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INTEGER&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IF&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; EXISTS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; index_expire_{&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}(expire_at);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;val&lt;/code&gt; column stores the cached value in a binary large object (BLOB) format, allowing it to handle various data types depending on the chosen serializer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Inserting or Updating Cache Entries&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INSERT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; OR&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; REPLACE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ${&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, val, created_at, expire_at) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VALUES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (?, ?, ?, ?)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;expire_at&lt;/code&gt; is pre-calculated based on the &lt;code&gt;ttl&lt;/code&gt; value in milliseconds, and &lt;code&gt;val&lt;/code&gt; is pre-checked by a function &lt;code&gt;isCacheable: (value: unknown) =&gt; boolean;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Retrieving Cache Entries&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ${&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ? &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;AND&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; expire_at &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ? &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;LIMIT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Returns one record that has not expired.&lt;/p&gt;
&lt;h2 id=&quot;auto-purge-and-batch-operations&quot;&gt;Auto Purge and Batch Operations&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. &lt;code&gt;purgeExpired&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; purgeExpired&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; now&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Date.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (now &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; lastPurgeTime &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 60&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 60&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; statement&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; db.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;prepare&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`DELETE FROM ${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;} WHERE expire_at &amp;#x3C; ?`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    statement.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(now);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    lastPurgeTime &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; now;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Keeps the cache clean and efficient by regularly removing stale entries, ensuring that the cache does not grow indefinitely and affect performance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Batch Set (mset)&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; mset&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;pairs&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;unknown&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;][], &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ttl&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ttlValue&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ttl &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ttl &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1000&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; defaultTtl;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (ttlValue &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; expireAt&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Date.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ttlValue;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; stmt&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `INSERT OR REPLACE INTO ${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;} (key, val, created_at, expire_at) VALUES ${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;pairs&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;(?, ?, ?, ?)&apos;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;, &apos;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; bindings&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; pairs.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;flatMap&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(([&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;isCacheable&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(value)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      throw&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; NoCacheableError&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&quot;${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&quot; is not a cacheable value`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [key, serializerAdapter.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;serialize&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(value), Date.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(), expireAt];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; statement&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; db.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;prepare&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(stmt);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  statement.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;bindings);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Improves efficiency by reducing the number of individual database operations. In the same way &lt;code&gt;mget&lt;/code&gt; executes one single query to returns valid records with the query:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ${&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; IN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (${placeholders}) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;AND&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; expire_at &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Bun’s SQLite implementation combined with efficient binary encoding formats like Msgpackr provides a powerful solution for building fast and compact cache stores.&lt;/p&gt;
&lt;p&gt;For the complete source code and implementation details, visitt: &lt;a href=&quot;https://github.com/sjdonado/cache-manager-bun-sqlite3&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;https://github.com/sjdonado/cache-manager-bun-sqlite3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You can also find the npm package &lt;a href=&quot;https://www.npmjs.com/package/cache-manager-bun-sqlite3&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;cache-manager-bun-sqlite3&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Happy hacking!&lt;/p&gt;</content:encoded></item><item><title>Should we leave behind the joy of curating our own content?</title><link>https://sjdonado.com/posts/2024-08-02-should-we-leave-behind-the-joy-of-curating-our-own-content/</link><guid isPermaLink="true">https://sjdonado.com/posts/2024-08-02-should-we-leave-behind-the-joy-of-curating-our-own-content/</guid><pubDate>Fri, 02 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For a while, I’ve been reading blog posts, articles, and comment threads about various tech and finance topics. I initially found it convenient to have a Medium subscription, as I thought it would allow me to have a feed automatically shaped by my interests. However, I ended up reading articles prioritized by clickbait, echo chambers and bias.&lt;/p&gt;
&lt;p&gt;So, I canceled my subscription and switched to Hacker News. I have discovered amazing independent writers discussing fascinating topics. Eventually, I started collecting these RSS feeds in one place, creating my own “Medium”.&lt;/p&gt;
&lt;p&gt;I believe that’s the true purpose of the internet: to be free to decide where to go and what to explore. There is joy in the journey more than the destination.&lt;/p&gt;
&lt;p&gt;If you’re curious about the blogs I follow, you can download and import the &lt;a href=&quot;https://gist.github.com/sjdonado/793f690e7783fe41eda06845998bc473&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;OPML file&lt;/a&gt; into your RSS reader of choice.&lt;/p&gt;</content:encoded></item><item><title>Tower of Hanoi in P5.js + WASM</title><link>https://sjdonado.com/posts/2023-05-08-tower-of-hanoi-in-p5-js-wasm/</link><guid isPermaLink="true">https://sjdonado.com/posts/2023-05-08-tower-of-hanoi-in-p5-js-wasm/</guid><description>Implementation of the Tower of Hanoi problem using P5.js for animation and Rust compiled to WebAssembly (WASM).</description><pubDate>Mon, 08 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import towerAnimation from ’../images/60ltbfz5s9nm9ejj07bf.gif?url’;
import wasmPackOutput from ’../images/j5sg6n8rehm3s1pegu66.gif?url’;&lt;/p&gt;
&lt;p&gt;In 2018, a professor at the Uni asked me to build a series of computer science games; he was looking for a very visual way to show students how the solution algorithms of &lt;a href=&quot;https://en.wikipedia.org/wiki/NP_(complexity)&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;NP problems&lt;/a&gt; (like the Knapsack Problem, Tower of Hanoi, Tic-Tac-Toe, etc.) behave and grow with their inputs.&lt;/p&gt;
&lt;img src=&quot;{towerAnimation}&quot; alt=&quot;Tower of Hanoi Animation&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Tower_of_Hanoi&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;Tower of Hanoi&lt;/a&gt; is a good exercise when students are getting started with recursion and vectors. The pegs are usually fixed at 3, so it is easy to define the base case, and the only variable is the number of disks. It is worth noting that there are other more efficient approaches, such as The Binary solution or the Gray-code solution, but this was a Complexity course and we wanted to show how exponential time can break your computer while keeping it as minimal as possible.&lt;/p&gt;
&lt;h2 id=&quot;vanilla-js-implementation&quot;&gt;Vanilla JS Implementation&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;../images/9jkliqf9hskgr3dcvuzd.png&quot; alt=&quot;Vanilla JS Implementation&quot;&gt;&lt;/p&gt;
&lt;p&gt;There was only one design requirement: to show the animated solutions of the problems in a web browser. At that time I was familiar with HTML5 but not with any modern UI frameworks. Frankly, I couldn’t imagine myself rendering objects from scratch in a plain HTML canvas. There had to be something out there that had already invented that wheel, and there was.&lt;/p&gt;
&lt;p&gt;I found &lt;a href=&quot;https://p5js.org/&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;P5.js&lt;/a&gt;, it is sort of the JS version of &lt;a href=&quot;https://processing.org/&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;Processing&lt;/a&gt; and it has everything I was looking for, a complete API for rendering, painting images and text, calling DOM elements and much more.&lt;/p&gt;
&lt;p&gt;The main core of the animation system was the &lt;code&gt;draw&lt;/code&gt; function provided by P5. It works as an infinite loop, so if we want to show the disks moving from &lt;code&gt;x1, y1&lt;/code&gt; to &lt;code&gt;x2, y2&lt;/code&gt;, we have to update the current position &lt;code&gt;x += speed y += speed&lt;/code&gt; where &lt;code&gt;0 &amp;#x3C; speed &amp;#x3C; 120&lt;/code&gt;. There are two types of motion, vertical and horizontal. An example of the former is:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (isMovingHorizontallly) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (animation.currentMove.start &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; animation.currentMove.end) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    refreshCanvas&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(p5);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    drawDiskByCoordinates&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      p5,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      animation.currentDisk,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      animation.currentMove.start,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      draw.topMargin,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    animation.currentMove.start &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; game.speed;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (animation.currentMove.start &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; animation.currentMove.end) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      animation.currentMove.start &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; animation.currentMove.end;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      animation.currentMove.down &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; draw.topMargin;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (animation.currentMove.start &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; animation.currentMove.end) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    refreshCanvas&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(p5);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    drawDiskByCoordinates&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      p5,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      animation.currentDisk,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      animation.currentMove.start,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      draw.topMargin,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    animation.currentMove.start &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; game.speed;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (animation.currentMove.start &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; animation.currentMove.end) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      animation.currentMove.start &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; animation.currentMove.end;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      animation.currentMove.down &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; draw.topMargin;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the &lt;code&gt;refreshCanvas&lt;/code&gt; function refreshes the canvas and redraws the towers filled by the disks, but not the one being animated.&lt;/p&gt;
&lt;p&gt;You may wonder how the disks know where to move, do they move at the same time as the Hanoi calculations? That would be cool, but unfortunately they don’t. We are limited by the recursive algorithm, so we have to wait until all calculations are done (until the call stack is empty).&lt;/p&gt;
&lt;p&gt;Therefore, the &lt;code&gt;draw&lt;/code&gt; function constantly checks if the array of moves &lt;code&gt;[towerFrom:towerTo, ...]&lt;/code&gt; generated by &lt;code&gt;recursiveHanoi&lt;/code&gt; is not empty. If it is, the animation starts.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; recursiveHanoi&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;A&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;C&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;B&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    moves.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;A&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;:&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; C&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    recursiveHanoi&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;A&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;B&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;C&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    moves.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;A&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;:&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; C&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    recursiveHanoi&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;B&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;C&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;A&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; generateHanoiArray&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;disksNumber&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  var&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; moves &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  recursiveHanoi&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, disksNumber);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; moves;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;recursiveHanoi&lt;/code&gt; did the job, but it was very inefficient: it couldn’t handle more than 14 disks (16K steps). The root cause was not in the algorithm itself, but in its execution in the V8 interpreter. Anyway, the same recursive function in C++ could run perfectly for more than 20 disks (aprox 16M of steps). So, running compiled bytecode in the browser was clearly the next step.&lt;/p&gt;
&lt;h2 id=&quot;wasm-pack&quot;&gt;wasm-pack&lt;/h2&gt;
&lt;p&gt;After four years, I found some time to pay that deb-tech (yes, quite a long time, eh). To make it fun I rewrote everything from scratch in SolidJS, which went smooth thanks to this amazing library &lt;a href=&quot;https://www.npmjs.com/package/p5js-wrapper&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;p5js-wrapper&lt;/a&gt;. For WASM, C++ is still a good choice, but what about Rust? I did some research and found &lt;a href=&quot;https://github.com/rustwasm/wasm-pack&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;wasm-pack&lt;/a&gt;. A few lines in the &lt;code&gt;cargo.toml&lt;/code&gt; file and we were ready to generate compiled + ready to import bytecode!&lt;/p&gt;
&lt;img src=&quot;{wasmPackOutput}&quot; alt=&quot;wasm-pack output&quot;&gt;
&lt;p&gt;Let’s dive into the configuration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;crate-type&lt;/code&gt; is set to indicate that the crate will be compiled as a dynamic library: &lt;code&gt;wasm-bindgen&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gloo-utils&lt;/code&gt; and &lt;code&gt;serde&lt;/code&gt; are included separately to avoid circular dependencies (&lt;a href=&quot;https://rustwasm.github.io/wasm-bindgen/reference/arbitrary-data-with-serde.html?highlight=serde#history&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;history&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;toml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;lib&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;path = &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;logic/lib.rs&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;crate-type = [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;cdylib&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;dependencies&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;wasm-bindgen = &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;0.2&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;getrandom = { version = &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;0.2.8&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, features = [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;js&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;gloo-utils = { version = &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;0.1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, features = [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;serde&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;serde = { version = &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;1.0&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, features = [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;derive&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the configuration is ready, we run &lt;code&gt;wasm-pack build -d wasm --no-typescript&lt;/code&gt; to compile. There is no advantage in generating ts files because we won’t use those definitions since the &lt;code&gt;get_moves&lt;/code&gt; function is gonna be called using a Web Worker.&lt;/p&gt;
&lt;p&gt;Then, to make the &lt;code&gt;wasm&lt;/code&gt; folder accessible to the solid app, we need to update the &lt;code&gt;vite.config.js&lt;/code&gt; file following the recommended setup from &lt;a href=&quot;https://github.com/Menci/vite-plugin-wasm&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;vite-plugin-wasm&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;diff&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;import { defineConfig } from &apos;vite&apos;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;import solidPlugin from &apos;vite-plugin-solid&apos;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt; import wasm from &apos;vite-plugin-wasm&apos;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt; import topLevelAwait from &apos;vite-plugin-top-level-await&apos;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;import path from &apos;path&apos;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;export default defineConfig({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  plugins: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    solidPlugin(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;   wasm(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;   topLevelAwait(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  server: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    port: 3000,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  build: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    target: &apos;esnext&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  resolve: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    alias: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      &apos;@src&apos;: path.resolve(__dirname, &apos;./src&apos;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;     &apos;@wasm&apos;: path.resolve(__dirname, &apos;./wasm&apos;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;  worker: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;    plugins: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;      wasm(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;      topLevelAwait(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The compiled output is now accessible as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { get_moves } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;@wasm/games&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But wait a second, we haven’t talked about this function yet, and why do we need a Web Worker? Let’s move on to the final implementation.&lt;/p&gt;
&lt;h2 id=&quot;hanoi-algorithm-in-rust&quot;&gt;Hanoi Algorithm in Rust&lt;/h2&gt;
&lt;p&gt;The tricky part of writing WASM code is how to interact with the outside world (I/O operations). Fortunately, our function is quite small, its conditions are mapped as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The function takes a &lt;code&gt;Number -&gt; i32&lt;/code&gt; argument: The number of disks.&lt;/li&gt;
&lt;li&gt;The function returns an array of strings serialised by the &lt;code&gt;serde_json&lt;/code&gt; crate (which allows you to convert a Rust data structure that implements the &lt;code&gt;serde::Serialize&lt;/code&gt; trait to a &lt;code&gt;serde_json::Value&lt;/code&gt; type, in this case a &lt;code&gt;JsValue&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And that’s it, inside the public fn, we can use regular Rust types like &lt;code&gt;Vect&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;rust&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; gloo_utils&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;format&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;JsValueSerdeExt&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; wasm_bindgen&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;prelude&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;#[wasm_bindgen]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;pub&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; fn&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get_moves&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; i32&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; JsValue&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    fn&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; move_tower&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; i32&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, from&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; i32&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, to&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; i32&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, aux&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; i32&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, moves&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x26;mut&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Vec&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            moves&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;format!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;{}:{}&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, from, to));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;            move_tower&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, from, aux, to, moves);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            moves&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;format!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;{}:{}&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, from, to));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;            move_tower&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, aux, to, from, moves);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; mut&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; moves &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Vec&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    move_tower&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;mut&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; moves);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    JsValue&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;from_serde&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;moves)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;unwrap&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;web-worker&quot;&gt;Web Worker&lt;/h2&gt;
&lt;p&gt;As mentioned above, the number of disks is exponentially related to the number of steps required in the solution. 20 disks are about 1M, but 24 are more than 16M (which is quite a lot 😰). In fact, the execution time of the recursive function exceeds the Doherty threshold (300ms). So, it becomes mandatory to execute it inside a non-blocking thread, for which we can use the Web Workers API.&lt;/p&gt;
&lt;p&gt;A new &lt;code&gt;.js&lt;/code&gt; file must be created for the worker anywhere in the &lt;code&gt;src&lt;/code&gt; folder.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { get_moves } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;@wasm/games&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;onmessage&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; moves&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get_moves&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(event.data.n);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  postMessage&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(moves);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, since the worker instance has nothing to do with the &lt;code&gt;solid&lt;/code&gt; component lifecycle, we can instance it outside.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; wasmWorker&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Worker&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; URL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;../workers/hanoi.js&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.url), {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  type: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;module&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, when the user clicks on the play button, the async function is called:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; getHanoiMoves&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  wasmWorker.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;onmessage&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; resolve&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(event.data);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  wasmWorker.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;postMessage&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ n });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;in-summary&quot;&gt;In Summary&lt;/h2&gt;
&lt;p&gt;NP problems are highly CPU-intensive, and &lt;code&gt;wasm-pack&lt;/code&gt; makes it easier to reduce the execution time. In addition, wrapping these operations in a Web Worker improves the user experience.&lt;/p&gt;
&lt;p&gt;A picture is worth a thousand words: for 20 disks the Vanilla JS implementation takes more than &lt;code&gt;312000ms&lt;/code&gt;, while Rust does it in under &lt;code&gt;200ms&lt;/code&gt; (tested on a Macbook Air M2).&lt;/p&gt;













&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Vanilla JS&lt;/th&gt;&lt;th&gt;WASM (Rust)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;../images/c70vx9b6ulzndq9w2aro.png&quot; alt=&quot;20 disks execution time - JS implementation&quot;&gt;&lt;/td&gt;&lt;td&gt;&lt;img src=&quot;../images/6gic9o198fj17aakii8q.png&quot; alt=&quot;20 disks execution time - WASM&quot;&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Source code available on Github: &lt;a href=&quot;https://github.com/sjdonado/cs-games&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;https://github.com/sjdonado/cs-games&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Sitemap generation by streaming from WordPress headless</title><link>https://sjdonado.com/posts/2024-11-03-sitemap-generation-by-streaming-from-wordpress-headless/</link><guid isPermaLink="true">https://sjdonado.com/posts/2024-11-03-sitemap-generation-by-streaming-from-wordpress-headless/</guid><description>Optimize sitemap generation by leveraging streaming and chunked transfer encoding.</description><pubDate>Sun, 03 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;code&gt;sitemap.xml&lt;/code&gt; file is very useful for search engines to understand which URLs are available for crawling. Therefore, when building a blog, generating this file can help boost SEO.&lt;/p&gt;
&lt;p&gt;Taking &lt;a href=&quot;https://sjdonado.com&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;this website&lt;/a&gt; as an example, the sitemap includes static URLs like &lt;code&gt;/home&lt;/code&gt;, &lt;code&gt;/digital-garden&lt;/code&gt;, as well as blog posts at &lt;code&gt;/posts/:slug&lt;/code&gt;. This is the output we need to generate:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;xml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;?&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;xml&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; version&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;1.0&quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; encoding&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;UTF-8&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;?&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;urlset&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; xmlns&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://www.sitemaps.org/schemas/sitemap/0.9&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;http://localhost:5173/&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;2024-11-04T06:32:35.319Z&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;monthly&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;1&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;http://localhost:5173/home&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;2024-11-04T06:32:35.319Z&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;monthly&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;0.8&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;http://localhost:5173/posts&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;2024-11-04T06:32:35.319Z&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;weekly&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;0.8&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;http://localhost:5173/digital-garden&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;2024-11-04T06:32:35.319Z&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;monthly&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;0.8&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;http://localhost:5173/social&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;2024-11-04T06:32:35.319Z&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;monthly&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;0.6&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;http://localhost:5173/slides&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;2024-11-04T06:32:35.319Z&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;monthly&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;0.6&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;https://sjdonado.com/posts/should-we-leave-behind-the-joy-of-curating-our-own-content&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;2024-11-02T12:38:11.000Z&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;weekly&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;0.8&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;https://sjdonado.com/posts/building-a-fast-and-compact-sqlite-cache-store&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;2024-10-31T17:06:21.000Z&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;weekly&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;0.8&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;https://sjdonado.com/posts/validated-forms-with-usefetcher-in-remix&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;2024-10-31T15:15:14.000Z&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;weekly&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;0.8&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;https://sjdonado.com/posts/fast-intuitive-smart-restaurant-search-engine-with-cloudflare-ai&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;2024-10-31T15:31:28.000Z&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;weekly&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;0.8&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;https://sjdonado.com/posts/htmx-with-bun-a-real-world-app&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;2024-11-03T14:02:46.000Z&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;weekly&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;0.8&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;https://sjdonado.com/posts/tower-of-hanoi-in-p5-js-wasm&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;2024-11-03T12:58:11.000Z&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;weekly&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;0.8&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;https://sjdonado.com/posts/self-hosted-password-manager-with-dokku&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;2024-10-31T15:55:16.000Z&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;weekly&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;0.8&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;urlset&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you might guess, the challenge of generating this file lies in how to fetch the posts using the WordPress REST API. Initially, we could opt to fetch all posts with &lt;code&gt;GET /wp-json/wp/v2/posts&lt;/code&gt; and iterate over the response in a separate backend:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;siteUrl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}/wp-json/wp/v2/posts?per_page=100&amp;#x26;_embed`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; posts&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; response.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sitemap &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;&amp;#x3C;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  sitemap &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;&amp;#x3C;urlset xmlns=&quot;http://www.sitemaps.org/schemas/sitemap/0.9&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; url&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; staticUrls) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    sitemap &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &amp;#x3C;url&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &amp;#x3C;loc&gt;${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&amp;#x3C;/loc&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &amp;#x3C;lastmod&gt;${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&amp;#x3C;/lastmod&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &amp;#x3C;changefreq&gt;${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&amp;#x3C;/changefreq&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &amp;#x3C;priority&gt;${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&amp;#x3C;/priority&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &amp;#x3C;/url&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    `&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; post&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; posts) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    sitemap &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &amp;#x3C;url&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &amp;#x3C;loc&gt;${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;siteUrl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}/posts/${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;slug&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&amp;#x3C;/loc&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &amp;#x3C;lastmod&gt;${&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Date&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;modified&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ||&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; post&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;date&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toISOString&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&amp;#x3C;/lastmod&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &amp;#x3C;changefreq&gt;weekly&amp;#x3C;/changefreq&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &amp;#x3C;priority&gt;0.8&amp;#x3C;/priority&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &amp;#x3C;/url&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    `&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  sitemap &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;&amp;#x3C;/urlset&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Response&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(sitemap, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    headers: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &apos;Content-Type&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;application/xml&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That approach works fine, and in the end, the most time-consuming operation is waiting for the API response, which tends to increase linearly as more posts are fetched. The question now is, can this be faster?&lt;/p&gt;
&lt;p&gt;The initial answer has to be yes, since we are adding overhead by not rendering this file directly from the WordPress environment, which is closest to the database. Moving from a headless approach to creating a WP action would be the ideal, fastest scenario. However, we don’t want to do that, we want to maintain a separation of concerns, where WP is our CMS, and the blog routes are rendered elsewhere.&lt;/p&gt;
&lt;p&gt;This approach leads us to consider rendering the sitemap file immediately as data is received from the database. Instead of waiting for the entire dataset, we can process posts in chunks as they are sent from the database. By setting up a new REST endpoint that returns posts as chunked data, a &lt;code&gt;ReadableStream&lt;/code&gt; can handle each chunk, appending it to the XML sitemap while streaming the file directly to the browser. This way, the browser starts receiving the sitemap file almost instantly, and the XML is built dynamically as new data arrives.&lt;/p&gt;
&lt;p&gt;It may sound a bit complicated, but think of it this way: We are streaming from the database to the browser by converting each post into a &lt;code&gt;urlset&lt;/code&gt; entry in the final XML file, each post goes through two streams:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;WP (stream 1, chunk post) -&gt; Backend (stream 2, chunk urlset) -&gt; Browser&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this in mind, let’s dive into the code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stream 1: WordPress endpoint&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Chunked transfer encoding allows chunks to be sent and received independently of one another. Therefore, we query the posts in batches, collect their information, and send them one by one in chunks. We need to ensure that each JSON-encoded chunk includes its size appended at the end.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;php&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; stream_posts&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Set the HTTP headers to indicate a stream&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    header&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Content-Type: application/json&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    header&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Transfer-Encoding: chunked&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    header&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Connection: keep-alive&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    header&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;X-Accel-Buffering: no&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Disable buffering in Nginx if applicable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Start output buffering and flush&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    ob_start&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Fetch posts in batches&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    $query &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; WP_Query&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &apos;post_type&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;post&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &apos;posts_per_page&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ($query&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;have_posts&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        $query&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;the_post&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        $post_data &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &apos;title&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;       =&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get_the_title&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &apos;link&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        =&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get_permalink&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &apos;excerpt&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;     =&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get_the_excerpt&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &apos;date&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        =&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get_the_date&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;c&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &apos;modified&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    =&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get_the_modified_date&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;c&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &apos;content&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;     =&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; apply_filters&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;the_content&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get_the_content&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        $json_chunk &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; json_encode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;($post_data) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // Output the chunk size in hexadecimal, followed by \r\n&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        echo&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; dechex&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;strlen&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;($json_chunk)) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\r\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // Output the chunk data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        echo&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $json_chunk &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\r\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // Flush the output buffer to send data immediately&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        ob_flush&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        flush&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Send the final chunk to indicate the end of the response&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    echo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\r\n\r\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Clean up&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    wp_reset_postdata&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    ob_end_flush&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    exit&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Terminate the script to prevent WordPress from adding any further output&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Stream 2: XML file&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We do the same as the WP endpoint, but on the blog rendering side. We open the second stream to the client while keeping the first stream connection open:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; sitemapStream&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ReadableStream&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    async&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; start&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;controller&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      controller.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;enqueue&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        `&amp;#x3C;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          `&amp;#x3C;urlset xmlns=&quot;http://www.sitemaps.org/schemas/sitemap/0.9&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; url&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; staticUrls) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; staticUrlItem&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          &amp;#x3C;url&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &amp;#x3C;loc&gt;${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;loc&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&amp;#x3C;/loc&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &amp;#x3C;lastmod&gt;${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;lastmod&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&amp;#x3C;/lastmod&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &amp;#x3C;changefreq&gt;${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;changefreq&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&amp;#x3C;/changefreq&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &amp;#x3C;priority&gt;${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;priority&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&amp;#x3C;/priority&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          &amp;#x3C;/url&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        `&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        controller.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;enqueue&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(staticUrlItem);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      for&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; post&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; streamFromWordpress&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/stream-posts&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; sitemapItem&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          &amp;#x3C;url&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &amp;#x3C;loc&gt;${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;link&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&amp;#x3C;/loc&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &amp;#x3C;lastmod&gt;${&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Date&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;modified&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ||&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; post&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;date&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;toISOString&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&amp;#x3C;/lastmod&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &amp;#x3C;changefreq&gt;weekly&amp;#x3C;/changefreq&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &amp;#x3C;priority&gt;0.8&amp;#x3C;/priority&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          &amp;#x3C;/url&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        `&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        controller.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;enqueue&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(sitemapItem);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      controller.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;enqueue&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&amp;#x3C;/urlset&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      controller.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;close&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Response&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(sitemapStream, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    headers: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &apos;Content-Type&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;application/xml&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that’s it. The result is an average reduction of more than 100ms.&lt;/p&gt;













&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Fetching All Posts&lt;/th&gt;&lt;th&gt;Streaming Posts&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;../images/Screenshot-2024-11-03-at-14.54.12-1024x659.png&quot; alt=&quot;Fetching All Posts&quot;&gt;&lt;/td&gt;&lt;td&gt;&lt;img src=&quot;../images/Screenshot-2024-11-03-at-14.54.59-1024x664.png&quot; alt=&quot;Streaming Posts&quot;&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;em&gt;Table: Fetching All Posts vs. Streaming Posts.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title>Managing 10k+ Visitors Overnight with Bare Metal</title><link>https://sjdonado.com/posts/2024-11-13-how-bare-metal-simplicity-handled-10k-visitors-overnight-without-breaking-the-bank/</link><guid isPermaLink="true">https://sjdonado.com/posts/2024-11-13-how-bare-metal-simplicity-handled-10k-visitors-overnight-without-breaking-the-bank/</guid><pubDate>Wed, 13 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;../images/Screenshot-2024-11-13-at-05.50.14-1024x543.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Yesterday, &lt;em&gt;I Don’t Have Spotify&lt;/em&gt; reached &lt;strong&gt;#2&lt;/strong&gt; on Hacker News, getting 700+ points &lt;a href=&quot;https://news.ycombinator.com/item?id=42110877&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;https://news.ycombinator.com/item?id=42110877&lt;/a&gt;. This brought in 13k views and over 10k unique visitors, pretty intense for a single day!&lt;/p&gt;
&lt;p&gt;What really stood out to me was how seamlessly the stack: HTMX, Bun, SQLite, and Dokku handled this traffic spike. No downtime, no slowdowns, no surprises. Just reliable performance.&lt;/p&gt;
&lt;p&gt;This highlights how far efficient, lightweight tools can take you. Instead of over-engineering with costly solutions, this setup handled a massive traffic spike overnight while keeping hosting costs under the price of a coffee per month.&lt;/p&gt;
&lt;p&gt;Sometimes, simplicity outshines the hype around “scale-ready” architectures.&lt;/p&gt;
&lt;p&gt;For anyone learning about infrastructure or preparing to launch a product, I thought this was worth sharing: scaling isn’t always about Kubernetes or microservices. You don’t need to invest heavily in infrastructure to achieve great results. The bare-metal approach can effectively handle real-world challenges, enabling quick and efficient scaling while keeping costs low. It’s a practical and accessible path to foster innovation.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I Don’t Have Spotify&lt;/em&gt; is open source and available here: &lt;a href=&quot;https://github.com/sjdonado/idonthavespotify&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;https://github.com/sjdonado/idonthavespotify&lt;/a&gt;. Feel free to explore, contribute, or use it as inspiration for your own projects.&lt;/p&gt;</content:encoded></item><item><title>Migrate remix eslint-config to ESLint v9</title><link>https://sjdonado.com/posts/2025-03-02-migrate-remix-eslint-config-to-eslint-v9/</link><guid isPermaLink="true">https://sjdonado.com/posts/2025-03-02-migrate-remix-eslint-config-to-eslint-v9/</guid><description>Custom ESLint v9 flat config for Remix from scratch.</description><pubDate>Sun, 02 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This weekend I finally found time to upgrade the old deprecated &lt;code&gt;@remix-run/eslint-config&lt;/code&gt; in my Remix projects (not yet migrated to React Router v7).&lt;/p&gt;
&lt;p&gt;I first attempted to remove the package entirely and adopt the recommended configuration from the official Remix template (&lt;a href=&quot;https://github.com/remix-run/remix/blob/main/templates/remix/.eslintrc.cjs&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;https://github.com/remix-run/remix/blob/main/templates/remix/.eslintrc.cjs&lt;/a&gt;). However, after running the ESLint migration tool (&lt;a href=&quot;https://eslint.org/docs/latest/use/getting-started&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;https://eslint.org/docs/latest/use/getting-started&lt;/a&gt;) &lt;code&gt;npm init @eslint/migrate-config&lt;/code&gt;, the output was problematic. This was likely because some dependencies weren’t yet compatible with ESLint’s new flat configuration system.&lt;/p&gt;
&lt;p&gt;Instead, I opted for a manual approach, installing and configuring each plugin individually. Here’s the outcome:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Install new dependencies&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --save-dev&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; @eslint/js&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; @remix-run/dev&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; @typescript-eslint/eslint-plugin&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; @typescript-eslint/parser&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; eslint&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; eslint-plugin-react&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; eslint-plugin-simple-import-sort&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; eslint-plugin-tailwindcss&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; globals&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tailwindcss&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; typescript-eslint&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Changes in the dev dependencies should look similar to these:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;diff&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;   &quot;devDependencies&quot;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;    &quot;@eslint/js&quot;: &quot;^9.20.0&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;-&lt;/span&gt; &quot;@remix-run/dev&quot;: &quot;^2.13.1&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;-&lt;/span&gt; &quot;@remix-run/eslint-config&quot;: &quot;^2.13.1&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;    &quot;@remix-run/dev&quot;: &quot;^2.15.3&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0;font-weight:bold&quot;&gt;@@ -89,16 +89,22 @@&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;    &quot;@typescript-eslint/eslint-plugin&quot;: &quot;^8.23.0&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;    &quot;@typescript-eslint/parser&quot;: &quot;^8.23.0&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;-&lt;/span&gt; &quot;eslint&quot;: &quot;^9.3.0&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;    &quot;eslint&quot;: &quot;^9.20.0&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;    &quot;eslint-plugin-react&quot;: &quot;^7.37.4&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;    &quot;globals&quot;: &quot;^15.14.0&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;    &quot;typescript-eslint&quot;: &quot;^8.23.0&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;   },&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Migrate old config file&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now we can remove &lt;code&gt;.eslintrc.cjs&lt;/code&gt; and replace it with the new flat config &lt;code&gt;eslint.config.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; jseslint &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;@eslint/js&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; reactPlugin &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;eslint-plugin-react&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; globals &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;globals&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; tseslint &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;typescript-eslint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/** &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;@type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; {import(&apos;eslint&apos;).Linter.Config[]}&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; default&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  { files: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;**/*.{js,mjs,cjs,ts,jsx,tsx}&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  { languageOptions: { globals: { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;globals.browser, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;globals.node } } },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  jseslint.configs.recommended,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  ...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;tseslint.configs.recommended,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  reactPlugin.configs.flat.recommended,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  reactPlugin.configs.flat[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;jsx-runtime&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Optionally, this is how it looks like if you would like to include the &lt;code&gt;eslint-plugin-tailwindcss&lt;/code&gt; and &lt;code&gt;eslint-plugin-simple-import-sort&lt;/code&gt; plugins:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; jseslint &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;@eslint/js&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; reactPlugin &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;eslint-plugin-react&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; simpleImportSort &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;eslint-plugin-simple-import-sort&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; globals &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;globals&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; tseslint &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;typescript-eslint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; tailwind &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;eslint-plugin-tailwindcss&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/** &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;@type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; {import(&apos;eslint&apos;).Linter.Config[]}&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; default&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  { files: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;**/*.{js,mjs,cjs,ts,jsx,tsx}&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  { languageOptions: { globals: { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;globals.browser, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;globals.node } } },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  jseslint.configs.recommended,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  ...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;tseslint.configs.recommended,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  reactPlugin.configs.flat.recommended,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  reactPlugin.configs.flat[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;jsx-runtime&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    plugins: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &quot;simple-import-sort&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: simpleImportSort,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    rules: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &quot;simple-import-sort/imports&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &quot;simple-import-sort/exports&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  ...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;tailwind.configs[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;flat/recommended&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>You are the easiest person to fool</title><link>https://sjdonado.com/posts/2025-03-09-you-are-the-easiest-person-to-fool/</link><guid isPermaLink="true">https://sjdonado.com/posts/2025-03-09-you-are-the-easiest-person-to-fool/</guid><description>We humans are remarkably good at deceiving ourselves.</description><pubDate>Sun, 09 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;The first principle is that you must not fool yourself—and you are the easiest person to fool. — Richard Feynman, Cargo Cult Science, Caltech commencement address, 1974.&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At the heart of Feynman’s warning lies a simple yet profound truth: we humans are remarkably good at deceiving ourselves. Our brains didn’t evolve primarily to discover objective truth, but to help us survive and sometimes that means prioritizing comfortable beliefs over accurate ones.&lt;/p&gt;
&lt;p&gt;This self-deception works through everyday mental shortcuts: we naturally seek out information that confirms what we already believe; we expertly weave narratives that make our self-interested decisions seem logical; and perhaps most dangerously, we convince ourselves we’re being objective when we’re anything but. What makes these processes so powerful is that they operate largely outside our awareness.&lt;/p&gt;
&lt;p&gt;Feynman recognized that this presents a special challenge to knowledge because we apply different standards to different sources. We’re quick to question claims from others while giving our own thinking process a free pass. This happens because we have insider access to our own motivations; we can craft justifications perfectly tailored to soothe our psychological defenses.&lt;/p&gt;
&lt;p&gt;Today, these cognitive blind spots are systematically amplified. Digital platforms optimize for engagement rather than accuracy, creating spaces where our biases are reinforced rather than challenged. Political divisions intensify as we interpret opposing viewpoints through increasingly hostile filters. Scientific questions become matters of identity rather than evidence.&lt;/p&gt;
&lt;p&gt;Science itself developed its methods: peer review, replication requirements, statistical controls; precisely as defenses against these human tendencies. Yet these safeguards only work when we acknowledge how easily we can fool ourselves.&lt;/p&gt;
&lt;p&gt;Feynman’s insight is not just about good science, it offers a fundamental approach to understanding reality: true knowledge begins with humility about our own mental limitations.&lt;/p&gt;
&lt;section data-footnotes=&quot;&quot; class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://calteches.library.caltech.edu/51/2/CargoCult.htm&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;Some remarks on science, pseudoscience, and learning how to not fool yourself. Caltech’s 1974 commencement address.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded></item><item><title>The Covering Index Paradox</title><link>https://sjdonado.com/posts/2025-04-10-the-covering-index-paradox/</link><guid isPermaLink="true">https://sjdonado.com/posts/2025-04-10-the-covering-index-paradox/</guid><description>SQLite index performance gap explained by B-tree height difference.</description><pubDate>Thu, 10 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;While recently tuning &lt;a href=&quot;https://github.com/sjdonado/bit&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;Bit’s&lt;/a&gt; performance, I encountered a bottleneck in one highly-selective query &lt;code&gt;SELECT id, url FROM links WHERE slug = (?) LIMIT 1&lt;/code&gt;. Initial optimizations, such as replacing UUIDs with &lt;code&gt;rowid&lt;/code&gt; and reducing the size of the slug column, significantly improved throughput. However, when I tested a covering index on &lt;code&gt;(slug, id, url)&lt;/code&gt;, I was surprised to find that it actually slowed down the queries, despite containing all the required columns and theoretically eliminating the need for one extra table lookups. Let’s dive into SQLite’s cost-based optimizer priorities to understand this counterintuitive behavior.&lt;/p&gt;
&lt;p&gt;For further context and reproducibility, here it is a separated database with a links table mirroring the original schema, populated with 10000 rows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IF&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; EXISTS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; links (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INTEGER&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; PRIMARY KEY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; AUTOINCREMENT,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    slug &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;TEXT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; UNIQUE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    url&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TEXT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOT NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;WITH&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; RECURSIVE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; generate_links &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    SELECT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; AS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;slug-&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ||&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; AS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; slug, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;https://sjdonado.com/link-&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ||&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; AS&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; url&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    UNION ALL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;slug-&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ||&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;https://sjdonado.com/link-&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ||&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; generate_links&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    WHERE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; links (slug, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; slug, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;url&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; generate_links;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SQLite automatically creates &lt;code&gt;sqlite_autoindex_links_1&lt;/code&gt; for the &lt;code&gt;UNIQUE&lt;/code&gt; constraint on slug:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;test.db&gt; SELECT * FROM pragma_index_list(&apos;links&apos;) AS il JOIN pragma_index_info(il.name) ON 1 = 1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+-----+--------------------------+--------+--------+---------+-------+-----+------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| seq | name                     | unique | origin | partial | seqno | cid | name |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+-----+--------------------------+--------+--------+---------+-------+-----+------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 0   | sqlite_autoindex_links_1 | 1      | u      | 0       | 0     | 1   | slug |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+-----+--------------------------+--------+--------+---------+-------+-----+------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1 row in set&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time: 0.004s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SQLite uses a default page size of 4KB, though this can be configured to any power of two between 512 bytes and 64KB. With the default configuration, each internal B-tree node can store approximately 500 child pointers, creating a substantial branching factor. This branching factor directly determines how many levels (the tree height) are needed to index a given number of rows, approximately log₅₀₀(N) levels for N rows. When querying data, each level traversed requires a separate page read operation, so a lower tree height dramatically improves performance. SQLite’s pager module caches recently accessed pages in memory, but the initial read of each required page still imposes I/O costs that scale with tree height.&lt;/p&gt;
&lt;h2 id=&quot;covering-indexes&quot;&gt;Covering Indexes&lt;/h2&gt;
&lt;p&gt;To execute our query, several sequential operations are performed. SQLite will perform a binary search on the index table using &lt;code&gt;SeekGE&lt;/code&gt;, looking for matches with the slug. It will stop there (because of the LIMIT clause, which sets the &lt;code&gt;IdxGT&lt;/code&gt; counter) and then access the corresponding table using &lt;code&gt;DeferredSeek&lt;/code&gt; with the &lt;code&gt;rowid&lt;/code&gt; (&lt;code&gt;IdxRowid&lt;/code&gt;) as a pointer:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;test.db&gt; EXPLAIN QUERY PLAN SELECT id, url FROM links WHERE slug = &apos;slug-9345&apos; LIMIT 1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+----+--------+---------+------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| id | parent | notused | detail                                                     |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+----+--------+---------+------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 4  | 0      | 39      | SEARCH links USING INDEX sqlite_autoindex_links_1 (slug=?) |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+----+--------+---------+------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1 row in set&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time: 0.005s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;test.db&gt; EXPLAIN SELECT id, url FROM links WHERE slug = &apos;slug-9345&apos; LIMIT 1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+------+--------------+----+----+----+-----------+----+---------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| addr | opcode       | p1 | p2 | p3 | p4        | p5 | comment |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+------+--------------+----+----+----+-----------+----+---------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 0    | Init         | 0  | 13 | 0  | &amp;#x3C;null&gt;    | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 1    | Integer      | 1  | 1  | 0  | &amp;#x3C;null&gt;    | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 2    | OpenRead     | 0  | 2  | 0  | 3         | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 3    | OpenRead     | 1  | 3  | 0  | k(1,)     | 2  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 4    | String8      | 0  | 2  | 0  | slug-9345 | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 5    | SeekGE       | 1  | 12 | 2  | 1         | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 6    | IdxGT        | 1  | 12 | 2  | 1         | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 7    | DeferredSeek | 1  | 0  | 0  | &amp;#x3C;null&gt;    | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 8    | IdxRowid     | 1  | 3  | 0  | &amp;#x3C;null&gt;    | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 9    | Column       | 0  | 2  | 4  | &amp;#x3C;null&gt;    | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 10   | ResultRow    | 3  | 2  | 0  | &amp;#x3C;null&gt;    | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 11   | DecrJumpZero | 1  | 12 | 0  | &amp;#x3C;null&gt;    | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 12   | Halt         | 0  | 0  | 0  | &amp;#x3C;null&gt;    | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 13   | Transaction  | 0  | 0  | 6  | 0         | 1  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 14   | Goto         | 0  | 1  | 0  | &amp;#x3C;null&gt;    | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+------+--------------+----+----+----+-----------+----+---------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;15 rows in set&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time: 0.005s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One can optimize query operations by avoiding table lookups through storing the &lt;code&gt;url&lt;/code&gt; value in a single concatenated key structure. This is possible due to all columns of a composite index are stored in both B-tree internal nodes and leaf nodes, ensuring the key is sorted on a left-to-right basis. This allows efficient handling of various data distributions without performance degradation, as long as the data follows a deterministic sorting order.&lt;/p&gt;
&lt;h2 id=&quot;outcome&quot;&gt;Outcome&lt;/h2&gt;
&lt;p&gt;Following that optimization idea, let’s introduce a covering index for our table:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt; CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; UNIQUE INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IF&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; EXISTS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; idx_links_slug_covering &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; links(slug, id, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;test.db&gt; SELECT * FROM pragma_index_list(&apos;links&apos;) AS il JOIN pragma_index_info(il.name) ON 1 = 1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+-----+--------------------------+--------+--------+---------+-------+-----+------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| seq | name                     | unique | origin | partial | seqno | cid | name |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+-----+--------------------------+--------+--------+---------+-------+-----+------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 0   | idx_links_slug_covering  | 1      | c      | 0       | 0     | 1   | slug |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 0   | idx_links_slug_covering  | 1      | c      | 0       | 1     | 0   | id   |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 0   | idx_links_slug_covering  | 1      | c      | 0       | 2     | 2   | url  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 1   | sqlite_autoindex_links_1 | 1      | u      | 0       | 0     | 1   | slug |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+-----+--------------------------+--------+--------+---------+-------+-----+------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;4 rows in set&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time: 0.002s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, with the new index available, and after performing the same query again, we notice that it was not chosen by SQLite’s query planner:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;test.db&gt; EXPLAIN QUERY PLAN SELECT id, url FROM links WHERE slug = &apos;slug-9345&apos; LIMIT 1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+----+--------+---------+------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| id | parent | notused | detail                                                     |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+----+--------+---------+------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 4  | 0      | 39      | SEARCH links USING INDEX sqlite_autoindex_links_1 (slug=?) |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+----+--------+---------+------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1 row in set&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time: 0.005s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can force the query to use our covering index, but there is no notable improvement:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;test.db&gt; EXPLAIN QUERY PLAN SELECT id, url FROM links INDEXED BY idx_links_slug_covering WHERE slug = &apos;slug-9345&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          LIMIT 1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+----+--------+---------+--------------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| id | parent | notused | detail                                                             |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+----+--------+---------+--------------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 3  | 0      | 40      | SEARCH links USING COVERING INDEX idx_links_slug_covering (slug=?) |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+----+--------+---------+--------------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1 row in set&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time: 0.005s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;test.db&gt; EXPLAIN SELECT id, url FROM links INDEXED BY idx_links_slug_covering WHERE slug = &apos;slug-9345&apos; LIMIT 1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+------+--------------+----+-----+----+-----------+----+---------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| addr | opcode       | p1 | p2  | p3 | p4        | p5 | comment |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+------+--------------+----+-----+----+-----------+----+---------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 0    | Init         | 0  | 12  | 0  | &amp;#x3C;null&gt;    | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 1    | Integer      | 1  | 1   | 0  | &amp;#x3C;null&gt;    | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 2    | OpenRead     | 1  | 931 | 0  | k(3,,,)   | 2  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 3    | String8      | 0  | 2   | 0  | slug-9345 | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 4    | SeekGE       | 1  | 11  | 2  | 1         | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 5    | IdxGT        | 1  | 11  | 2  | 1         | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 6    | IdxRowid     | 1  | 3   | 0  | &amp;#x3C;null&gt;    | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 7    | Column       | 1  | 2   | 4  | &amp;#x3C;null&gt;    | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 8    | ResultRow    | 3  | 2   | 0  | &amp;#x3C;null&gt;    | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 9    | DecrJumpZero | 1  | 11  | 0  | &amp;#x3C;null&gt;    | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 10   | Next         | 1  | 5   | 0  | &amp;#x3C;null&gt;    | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 11   | Halt         | 0  | 0   | 0  | &amp;#x3C;null&gt;    | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 12   | Transaction  | 0  | 0   | 6  | 0         | 1  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;| 13   | Goto         | 0  | 1   | 0  | &amp;#x3C;null&gt;    | 0  | &amp;#x3C;null&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;+------+--------------+----+-----+----+-----------+----+---------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;14 rows in set&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time: 0.007s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The main difference in this last query is that instruction 7, &lt;code&gt;Column&lt;/code&gt; retrieves the slug value directly from the index. Since &lt;code&gt;DeferredSeek&lt;/code&gt; is no longer required, there is one operation less. However, the query is still slower. Why?&lt;/p&gt;
&lt;p&gt;The answer lies in the page size and density difference between these index types. Despite both indexes having the same B-tree height for our 1000-row dataset, the covering index requires &lt;strong&gt;triple the storage space&lt;/strong&gt; (18 pages vs 6 pages). This directly translates to increased I/O operations during query execution.&lt;/p&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Index&lt;/th&gt;&lt;th&gt;Entry Size&lt;/th&gt;&lt;th&gt;Pages (N=10000)&lt;/th&gt;&lt;th&gt;B-tree Height&lt;/th&gt;&lt;th&gt;Entries/Page&lt;/th&gt;&lt;th&gt;Complexity&lt;/th&gt;&lt;th&gt;Page Reads&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;rowid&lt;/code&gt;&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;O(1)&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;sqlite_autoindex_links_1&lt;/code&gt;&lt;/td&gt;&lt;td&gt;24 bytes&lt;/td&gt;&lt;td&gt;60&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;~166&lt;/td&gt;&lt;td&gt;O(log N)&lt;/td&gt;&lt;td&gt;2-3&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;idx_links_slug_covering&lt;/code&gt;&lt;/td&gt;&lt;td&gt;72 bytes&lt;/td&gt;&lt;td&gt;182&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;~55&lt;/td&gt;&lt;td&gt;O(log N)&lt;/td&gt;&lt;td&gt;4-5&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;For larger datasets, this density disparity would likely increase the B-tree height of the covering index before the autoindex, creating an even larger performance gap. The covering index approach only becomes advantageous when the cost of the additional table lookup &lt;code&gt;DeferredSeek&lt;/code&gt; exceeds the cost of traversing a taller, less dense B-tree structure, typically with very large tables or when retrieving multiple wide columns that would make table lookups particularly expensive.&lt;/p&gt;
&lt;p&gt;What a paradox, one expect a denser index is expected to be more efficient, yet it can lead to a taller B-tree and potentially worse performance.&lt;/p&gt;</content:encoded></item><item><title>Consistency isn’t repetition</title><link>https://sjdonado.com/posts/2025-04-13-consistency-isnt-repetition/</link><guid isPermaLink="true">https://sjdonado.com/posts/2025-04-13-consistency-isnt-repetition/</guid><description>We are terrible at being the same person two days in a row.</description><pubDate>Sun, 13 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;“&lt;em&gt;Tomorrow, I’ll wake up at 06:00, meditate, work out, and finally crush those overdues in my Todo list&lt;/em&gt;”. That’s what I often tell myself, but when tomorrow arrives, reality throws a wrench into my well-laid plans, and I’m left wondering why I can’t stick to my script.&lt;/p&gt;
&lt;p&gt;The mismatch between plans and reality lies in the way that life operates, much more like a relay than a solo run, where each day, a new runner (“today-you”) takes the baton, you are not the same person you were yesterday, but a subtly different version with fresh energy, priorities, and patience.&lt;/p&gt;
&lt;p&gt;This isn’t about laziness. It’s about our evolutionary wiring for &lt;em&gt;instant gratification&lt;/em&gt;, our brains prioritize short-term rewards over long-term gains&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. We binge Netflix tonight, aware future-us will pay the price, but tomorrow’s discomfort feels abstract today. This isn’t a moral failure, it’s biology.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Consistency isn’t repetition, it’s continuity.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Think of momentum not as a linear path, but as a game where every small effort unlocks future advantages. Instead of demanding identical effort daily, focus on leaving momentum for the next iteration of you. Skipped the gym? Do five push-ups. Overwhelmed by work? Write one sentence. These aren’t compromises, they’re lifelines. Like saving progress in a video game, they ensure tomorrow’s player doesn’t reboot at level one.&lt;/p&gt;
&lt;p&gt;If you feel disconnected from your future self, consider this: small investments build trust in your capacity to grow. Reframe future-you as a family member, you wouldn’t abandon a sibling to chaos. Meal prepping, clearing your desk, or evening journaling aren’t tedious chores. They’re strategic passes to a teammate you’re rooting for, even if you’re still building a relationship with them.&lt;/p&gt;
&lt;p&gt;You don’t have to win the whole game today, just set the next player up for an easier round. A failed attempt teaches tomorrow’s self what not to repeat. A half-finished task is a foothold for progress. Action, even messy action, is data, not the final verdict.&lt;/p&gt;
&lt;p&gt;Play your turn fully, savor the moment, and then hand off the controller. &lt;strong&gt;Momentum compounds&lt;/strong&gt; when today’s moves, no matter how small, lay the groundwork for tomorrow’s success.&lt;/p&gt;
&lt;section data-footnotes=&quot;&quot; class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://www.jneurosci.org/content/jneuro/30/18/6178.full.pdf&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;Uncovering the Neural Basis of Resisting Immediate Gratification while Pursuing Long-Term Goals&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded></item><item><title>No Freemium. Not for Free. Free</title><link>https://sjdonado.com/posts/2025-08-02-no-freemium-no-for-free-free/</link><guid isPermaLink="true">https://sjdonado.com/posts/2025-08-02-no-freemium-no-for-free-free/</guid><description>Setting software free is how we turn one person&apos;s solution into humanity&apos;s progress.</description><pubDate>Sat, 02 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The freemium model is marketing, a clever funnel. Users get to play with the product until they hit the walls: a storage limit, an export button grayed out, or a 14‑day timer ticking down. That frustration is intentional, it turns “just curious” into “just paid”. And that revenue does not just cover the cost of those extra features, it props up an MVP weighed down with tech debt, growing infrastructure bills, and the payroll needed to keep chasing growth.&lt;/p&gt;
&lt;p&gt;That is the real game, growth, not craft. New features to attract new users, even if the foundation cracks. The goal is not to make it sustainable, it is to make it bigger, to chase the next‑Google dream, or at least look valuable enough for an exit.&lt;/p&gt;
&lt;p&gt;Free software comes from a different impulse. You build it for yourself first. You polish it because you rely on it. You craft it because creative minds hate to leave rough edges unshaped. And when you open it up, you are not giving away charity, you are inviting the world to help make it even better.&lt;/p&gt;
&lt;p&gt;If you are proud of your work, you want to see it used, you want to see it free. There is nothing better than watching your solution live in the wild. More users bring more edge cases, and each one nudges the software into something great. That kind of refinement only happens when the backbone is built carefully, without the rush of quarterly deadlines, when there is room to focus on the invisible parts, the ones that make a tool last.&lt;/p&gt;</content:encoded></item><item><title>Quieter Social Networking</title><link>https://sjdonado.com/posts/2025-10-29-quieter-social-networking/</link><guid isPermaLink="true">https://sjdonado.com/posts/2025-10-29-quieter-social-networking/</guid><description>Blocking recommendation algorithms while maintaining an online presence.</description><pubDate>Wed, 29 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’ve been gradually stepping away from doomscrolling for a while now. I got rid off my Facebook, Instagram, TikTok, and Twitter accounts. Plus, their domains are blacklisted through Screen Time’s web content restrictions, so it’s impossible for me to open any related links in the browser or via web viewers.&lt;/p&gt;
&lt;p&gt;However, I kept YouTube and LinkedIn because they offer content I intentionally want to see. The problem is that whenever I access them, I’m at risk of falling into recommendation algorithm traps, and I’m left with nothing but my willpower.&lt;/p&gt;
&lt;p&gt;Until last week, when I finally found a solution by reading Herman’s post, &lt;a href=&quot;https://herman.bearblog.dev/being-present/&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;Smartphones and being present&lt;/a&gt;. The post shows how to use uBlock Origin filters to remove elements from the DOM, which means I can access a clean version of the app through the web browser. For example, after uninstalling the YouTube app, when I go into Safari, I see a customized version: No home feed, no short videos, and no suggested videos.&lt;/p&gt;
&lt;p&gt;The ruleset I currently have on &lt;a href=&quot;https://gist.github.com/sjdonado/5d8344d4571c362d52bd0815911959b6&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;quieter-social-networking-custom-filters.txt&lt;/a&gt; looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/Screenshot-2025-10-28-at-16-49-03.png&quot; alt=&quot;uBlock custom filters&quot;&gt;&lt;/p&gt;
&lt;p&gt;For more filters to declutter the web with uBlock, I highly recommend this repo: &lt;a href=&quot;https://github.com/yokoffing/filterlists&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;github.com/yokoffing/filterlists&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Besides that, I thought about replacing Twitter with Bluesky but realized it wouldn’t change the habit. It still wouldn’t let me choose when to be informed instead of being flooded by constant headlines. So I picked the recently launched &lt;a href=&quot;https://news.kagi.com&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;Kagi News&lt;/a&gt;, the old friend Hacker News and a few &lt;a href=&quot;https://sjdonado.com/posts/2024-08-02-should-we-leave-behind-the-joy-of-curating-our-own-content/&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;RSS blogs curated by myself&lt;/a&gt;. Together, they offer a focused, manageable view of the world without hijacking curiosity. When I want to explore, I do it deliberately, not through a feed.&lt;/p&gt;
&lt;p&gt;With this minimal setup of content restrictions, browser-only use, and custom uBlock filters, I’ve found a quieter way to be present online. I hope it helps others do the same.&lt;/p&gt;</content:encoded></item><item><title>The tools changed because the job changed</title><link>https://sjdonado.com/posts/2026-02-11-changes-in-my-development-setup/</link><guid isPermaLink="true">https://sjdonado.com/posts/2026-02-11-changes-in-my-development-setup/</guid><description>From optimizing keystrokes to optimizing decisions.</description><pubDate>Thu, 12 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;By the end of 2022, I had one clear goal: be faster. I had lots of ideas but struggled to quickly prototype them; my code editor was a big part of the problem, tediously slow and eating all my RAM. Neovim was the answer, typing as fast as thinking. Plus, my laptop was flying, just one terminal tab, a handful of TUIs, nothing else.&lt;/p&gt;
&lt;p&gt;I spent the next years writing Lua scripts to shape my workflow. It was fun, and I valued customization more than the maintenance cost. It worked, until the job outgrew it. Growing in my career meant more code reviews, more discussions, more jumping between branches. &lt;code&gt;git stash&lt;/code&gt; falls short when you are juggling three features and a hotfix, it introduces the same kind of barrier as leaving the terminal. No amount of keymaps can fix a workflow problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Git worktrees&lt;/strong&gt; solved it. One worktree per branch, each pinned to a tmux session. Switching context became switching panes. The friction dropped and I could finally collaborate at the pace the role demanded.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
&lt;strong&gt;2022 setup&lt;/strong&gt;: &lt;a href=&quot;https://github.com/tmux/tmux&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;tmux&lt;/a&gt;, &lt;a href=&quot;https://neovim.io&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;Neovim&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Somewhere along the way AI went from an assistant to the thing that writes most of the code. That changed the job more than any tool swap ever did. I spend less time in my editor and more time in the planning phase, whiteboarding, sketching interfaces, writing specs before touching a file. Reviewing other people’s work still takes the same effort, maybe more, because AI-generated code is fluent but not always thoughtful. The bottleneck moved upstream, and I think that is true for everyone, not just me. The kind of thinking that used to be reserved for staff engineers, system design, trade-off analysis, deciding what not to build, is quietly becoming the baseline expectation at every level.&lt;/p&gt;
&lt;p&gt;So I adjusted my tooling. Zed won me over with its first-class vim motions, and it stuck. It is the fastest editor experience I have found outside a TUI. Their &lt;a href=&quot;https://zed.dev/acp&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;ACP&lt;/a&gt; simplifies working across multiple worktrees. My workflow now looks like this: manage sessions in tmux, open Zed and inside it launch the agent chat and the integrated terminal. Once I am done with a branch, I close the tab. Multiple project tabs in one window means no switching between editor instances.&lt;/p&gt;
&lt;p&gt;I also mapped every app I use daily to a hotkey in Raycast, all following the same pattern: &lt;code&gt;&amp;#x3C;leader&gt;&lt;/code&gt; + one letter. Zed: &lt;code&gt;&amp;#x3C;leader&gt;;&lt;/code&gt;. &lt;a href=&quot;https://helium.app&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;Helium&lt;/a&gt;: &lt;code&gt;&amp;#x3C;leader&gt;h&lt;/code&gt;. Terminal: &lt;code&gt;&amp;#x3C;leader&gt;/&lt;/code&gt;. Slack: &lt;code&gt;&amp;#x3C;leader&gt;s&lt;/code&gt;. No transition animations, just jump where I need to. Raycast’s clipboard manager is a delightful tool, I save entries as snippets and rely on them constantly.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
&lt;strong&gt;2026 setup&lt;/strong&gt;: &lt;a href=&quot;https://github.com/sjdonado/workspace&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;workspace&lt;/a&gt;, &lt;a href=&quot;https://zed.dev&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;Zed&lt;/a&gt;, &lt;a href=&quot;https://www.raycast.com&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;Raycast&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Cannot deny I feel nostalgia when I open my &lt;a href=&quot;https://github.com/sjdonado/dotfiles&quot; rel=&quot;noreferrer&quot; target=&quot;_blank&quot;&gt;dotfiles&lt;/a&gt;, I still use Neovim to edit them. But that trade-off no longer holds. I do not need to fine-tune keymaps, chase LSP edge cases, configure Treesitter grammars, or wire up DAP. The tools I care about now are the ones that let me think, not the ones that let me type.&lt;/p&gt;
&lt;p&gt;Looking back, the pattern is simple. First I optimized for typing speed, then for context-switching speed, now for decision-making speed. Each time, the tools changed because the work changed first. AI did not make the coding part disappear, it just made the engineering part around it much bigger. The tools followed, as they always do.&lt;/p&gt;</content:encoded></item></channel></rss>