<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Garrett Mills</title>
    <description>The latest articles on DEV Community by Garrett Mills (@garrettmills).</description>
    <link>https://dev.to/garrettmills</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F523305%2F5f90bea0-2649-4fa7-9a9f-3a3e05760a2a.jpg</url>
      <title>DEV Community: Garrett Mills</title>
      <link>https://dev.to/garrettmills</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/garrettmills"/>
    <language>en</language>
    <item>
      <title>Salvaging a Corrupted Table from PostgreSQL</title>
      <dc:creator>Garrett Mills</dc:creator>
      <pubDate>Sat, 11 Jan 2025 22:04:25 +0000</pubDate>
      <link>https://dev.to/garrettmills/salvaging-a-corrupted-table-from-postgresql-17ep</link>
      <guid>https://dev.to/garrettmills/salvaging-a-corrupted-table-from-postgresql-17ep</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post originally appeared on &lt;a href="https://garrettmills.dev/blog/2025/01/11/Salvaging-a-Corrupted-Table-from-PostgreSQL/" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyx55qctz53sg27yczp5s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyx55qctz53sg27yczp5s.png" width="800" height="87"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;b&gt;DO NOT DO THIS...&lt;/b&gt; well ever really, but especially on a server with failing disks. This is done on a server with perfectly fine disks, but corrupted Postgres blocks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I spend a lot of time in my professional work and my home lab trying to learn and implement the “correct” or “responsible” way of implementing a solution — highly-available deployments, automated and tested backups, infrastructure-as-code, &amp;amp;c.&lt;/p&gt;

&lt;p&gt;This is not that.&lt;/p&gt;

&lt;p&gt;This is a very dirty, no-holds-barred, absolutely insane thing to do, and if you’re working in any kind of environment that &lt;em&gt;matters&lt;/em&gt;, you should &lt;a href="https://wiki.postgresql.org/wiki/Corruption" rel="noopener noreferrer"&gt;read this&lt;/a&gt; and hire a professional.&lt;/p&gt;

&lt;p&gt;For unimportant reasons, I’ve been dealing with data corruption on the Postgres server in my home lab. The server was terminated uncleanly a couple times and the disk data was corrupted. Because there’s nothing more permanent than a temporary solution, I did not have backups for this server.&lt;/p&gt;

&lt;p&gt;For most of the data, I was able to use &lt;code&gt;pg_dump&lt;/code&gt; to dump the schemata and data and re-import it into my new Postgres server (which, yes, has backups configured now).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pg_dump -U postgres -h localhost my_database &amp;gt; my_database.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For databases with corrupted tables, though, &lt;code&gt;pg_dump&lt;/code&gt; fails out with this unsettling error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; pg_dump -U postgres -h localhost www_p1 &amp;gt; www_p1.sql
pg_dump: error: Dumping the contents of table "page_views" failed: PQgetResult() failed.
pg_dump: detail: Error message from server: ERROR:  invalid page in block 31869 of relation base/16384/16417
pg_dump: detail: Command was: COPY public.page_views (page_view_id, visited_at, hostname, ip, method, endpoint, user_id, xhr) TO stdout;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(…yes, that’s the database for my personal website. 👀) Somewhat to my surprise, I couldn’t find many details/strategies for how to “best effort” recover data from a corrupt Postgres table, so here we go.&lt;/p&gt;

&lt;p&gt;Luckily, since the corruption was the result of unclean Postgres exits and not bad physical disks, it only affected table(s) with frequent writes at the time. In this case, that was the &lt;code&gt;sessions&lt;/code&gt; table and the &lt;code&gt;page_views&lt;/code&gt; table. The &lt;code&gt;sessions&lt;/code&gt; table is entirely disposable — I just re-created it empty on the new server and moved on with my life.&lt;/p&gt;

&lt;p&gt;It wouldn’t be the end of the world if I lost the &lt;code&gt;page_views&lt;/code&gt; table, but there are some 6.5 million historical page-views recorded in that table that would kind of suck to lose. So… let’s do some sketchy shit.&lt;/p&gt;

&lt;p&gt;My goal here isn’t to recover the entire table. If that was the goal, I would’ve stopped and hired a professional. Instead, my goal is to recover as many rows of the table as possible.&lt;/p&gt;

&lt;p&gt;One reason &lt;code&gt;pg_dump&lt;/code&gt; fails is because it tries to read the data using a cursor, which fails when the fundamental assumptions of Postgres are violated (e.g. bad data in disk blocks, invalid indices).&lt;/p&gt;

&lt;p&gt;My strategy here is to create a 2nd table on the bad server with the same schema, then loop over each row in the &lt;code&gt;page_views&lt;/code&gt; individually and insert them into the clean table, skipping rows in disk blocks with bad data. Shout out to &lt;a href="https://stackoverflow.com/a/63905054/4971138" rel="noopener noreferrer"&gt;this Stack Overflow answer&lt;/a&gt; that loosely inspired this strategy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;PROCEDURE&lt;/span&gt; &lt;span class="n"&gt;pg_recover_proc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt;
    &lt;span class="n"&gt;cnt&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="c1"&gt;-- Get the maximum page_view_id from the page_views table&lt;/span&gt;
    &lt;span class="n"&gt;cnt&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page_view_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;page_views&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;-- Loop through the page_views table in reverse order by page_view_id&lt;/span&gt;
    &lt;span class="n"&gt;LOOP&lt;/span&gt;
        &lt;span class="k"&gt;BEGIN&lt;/span&gt;
            &lt;span class="c1"&gt;-- Insert the row with the current page_view_id into page_views_recovery&lt;/span&gt;
            &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;page_views_recovery&lt;/span&gt;
            &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;page_views&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;page_view_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cnt&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;entrypoint&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="c1"&gt;-- Decrement the counter&lt;/span&gt;
            &lt;span class="n"&gt;cnt&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cnt&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="c1"&gt;-- Exit the loop when cnt &amp;lt; 1&lt;/span&gt;
            &lt;span class="n"&gt;EXIT&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;cnt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;EXCEPTION&lt;/span&gt;
            &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;OTHERS&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
                &lt;span class="c1"&gt;-- Handle exceptions (e.g., data corruption)&lt;/span&gt;
                &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;POSITION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'block'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SQLERRM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;POSITION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status of transaction'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SQLERRM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;POSITION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'memory alloc'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SQLERRM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;POSITION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'data is corrupt'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SQLERRM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;POSITION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MultiXactId'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SQLERRM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
                    &lt;span class="n"&gt;RAISE&lt;/span&gt; &lt;span class="n"&gt;WARNING&lt;/span&gt; &lt;span class="s1"&gt;'PGR_SKIP: %'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cnt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="n"&gt;cnt&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cnt&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="k"&gt;CONTINUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;ELSE&lt;/span&gt;
                    &lt;span class="n"&gt;RAISE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;MOD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cnt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
            &lt;span class="n"&gt;RAISE&lt;/span&gt; &lt;span class="n"&gt;WARNING&lt;/span&gt; &lt;span class="s1"&gt;'PGR_COMMIT: %'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cnt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are some cool and absolutely terrible things here. In modern versions of Postgres, stored procedures can periodically commit their in-progress top-level transactions by calling &lt;code&gt;COMMIT&lt;/code&gt; repeatedly. I’m (ab)using this here to flush the recovered rows to the new table as the procedure runs in case it fails partway through.&lt;/p&gt;

&lt;p&gt;I'm doing some rough string analysis for error messages related to corrupt data and skipping the current row if that's the case. Another interesting edge-case: a couple times, I ran into a case where the &lt;code&gt;INSERT&lt;/code&gt; into the recovery table failed because the &lt;code&gt;SELECT&lt;/code&gt; query against the bad table was returning &lt;code&gt;null&lt;/code&gt; values, even though that should technically never be possible. Told you we're violating some foundational assumptions about Postgres here. Adding an &lt;code&gt;is not null&lt;/code&gt; to a different non-null column helped avoid this.&lt;/p&gt;

&lt;p&gt;My original draft of this procedure was designed to keep looping and just skip the fatal errors caused by disk corruption (the various dirty &lt;code&gt;POSITION&lt;/code&gt; checks in the error handler).&lt;/p&gt;

&lt;p&gt;Quickly, however, I ran into a new error:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL Error [57P03]: FATAL: the database system is in recovery mode&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Turns out, if you keep intentionally forcing Postgres to try to read data from bad disk blocks, eventually its internal data structures hit an inconsistent state and the server process restarts itself out for safety.&lt;/p&gt;

&lt;p&gt;This is (obviously) a problem because we can’t catch that and force the procedure to keep running against its will. So instead I resorted to adding &lt;code&gt;IF&lt;/code&gt; conditions to manually skip over primary key regions that caused the server process to crash. (I told you this was crazy.)&lt;/p&gt;

&lt;p&gt;Every time the server would crash, I would dump out the rows I’d recovered so far, just in case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pg_dump -U postgres -h localhost --table page_views2 www_p1 &amp;gt; page_views2-1.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I’d skip a new region of primary key, drop and re-create the recovery table, and try again. Why drop and re-create it? Because I discovered that when the server process crashed, it would occasionally write bad data to the &lt;em&gt;recovery&lt;/em&gt; table, which is obviously no good:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pg_dump: error: Dumping the contents of table "page_views_recovery" failed: PQgetResult() failed.
pg_dump: detail: Error message from server: ERROR:  invalid memory alloc request size 18446744073709551613
pg_dump: detail: Command was: COPY public.page_views_recovery (page_view_id, visited_at, hostname, ip, method, endpoint, user_id, xhr) TO stdout;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Predictably, this got really annoying to do by hand, so I did what any good Linux nerd would do and wrote a script for it, which &lt;a href="https://code.garrettmills.dev/garrettmills/pg-recover" rel="noopener noreferrer"&gt;you can find here&lt;/a&gt;. The gist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./pg-recover.sh postgres localhost www_p1 page_views page_view_id entrypoint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of the 6,628,903 rows in the corrupt table, I was able to recover 6,444,118 of them. You know what they say — if it’s stupid and it works, it’s still stupid and you’re just lucky.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>linux</category>
      <category>database</category>
    </item>
    <item>
      <title>Mitigating the iconv Vulnerability for PHP (CVE-2024-2961)</title>
      <dc:creator>Garrett Mills</dc:creator>
      <pubDate>Tue, 23 Apr 2024 02:33:44 +0000</pubDate>
      <link>https://dev.to/garrettmills/mitigating-the-iconv-vulnerability-for-php-cve-2024-2961-44ha</link>
      <guid>https://dev.to/garrettmills/mitigating-the-iconv-vulnerability-for-php-cve-2024-2961-44ha</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post originally appeared on my blog, &lt;a href="https://garrettmills.dev/blog/2024/04/22/Mitigating-the-iconv-Vulnerability-for-PHP-CVE-2024-2961/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Recently, &lt;a href="https://www.openwall.com/lists/oss-security/2024/04/18/4" rel="noopener noreferrer"&gt;CVE-2024-2961&lt;/a&gt; was released which identifies a buffer overflow vulnerability in GNU libc versions 2.39 and below when converting charsets to certain Chinese Extended encodings.&lt;/p&gt;

&lt;p&gt;This vulnerability affects PHP when &lt;code&gt;iconv&lt;/code&gt; is used to translate request encodings to/from the affected charsets and has the potential to be wide-ranging (e.g. the latest &lt;code&gt;wordpress:apache&lt;/code&gt; image has &lt;code&gt;iconv&lt;/code&gt; with the vulnerable charsets enabled).&lt;/p&gt;

&lt;p&gt;Obviously, the best mitigation is to update to a patched version of &lt;code&gt;glibc&lt;/code&gt;. However, if you are unable to (or it's not available on your OS yet), you can mitigate this issue by disabling the affected charsets in &lt;code&gt;gconv&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I had a really hard time finding information on how to check for and mitigate this issue at the OS-level (possibly because the researcher who discovered the CVE is presently &lt;em&gt;teasing&lt;/em&gt; details about the PHP exploit for his &lt;a href="https://www.offensivecon.org/speakers/2024/charles-fol.html" rel="noopener noreferrer"&gt;talk at a conference&lt;/a&gt;... 3 weeks after the CVE was announced. 🙄)&lt;/p&gt;

&lt;p&gt;I've collected my notes here, in case they might be useful for someone else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check if your OS is vulnerable
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ldd &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line of the linker version info should include the version of glibc (either as &lt;code&gt;GLIBC&lt;/code&gt; or &lt;code&gt;GNU libc&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Example from Debian 12:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ldd (Debian GLIBC 2.36-9+deb12u4) 2.36
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example from Rocky Linux 9:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ldd (GNU libc) 2.34
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You can also use your package manager to check (for example, &lt;code&gt;rpm -q glibc&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you are using &lt;code&gt;glibc&lt;/code&gt; 2.39 or older, then the &lt;code&gt;ISO-2022-CN-EXT&lt;/code&gt; encodings are vulnerable for your system's &lt;code&gt;iconv&lt;/code&gt; and &lt;code&gt;gconv&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check for bad encodings
&lt;/h2&gt;

&lt;p&gt;Check if the vulnerable encodings are enabled in &lt;code&gt;iconv&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iconv &lt;span class="nt"&gt;-l&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'CN-?EXT'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If they are, you will see an output like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ISO-2022-CN-EXT//
ISO2022CNEXT//
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Disable bad encodings
&lt;/h2&gt;

&lt;p&gt;We can modify the &lt;code&gt;gconv-modules&lt;/code&gt; configuration to disable the affected charsets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /usr/lib/x86_64-linux-gnu/gconv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This might be in slightly different locations for exotic systems. Try &lt;code&gt;find / -name gconv-modules&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Disable the offending encodings in the &lt;code&gt;gconv-modules&lt;/code&gt; config file. This will either be in &lt;code&gt;gconv-modules&lt;/code&gt; directly, or in something like &lt;code&gt;gconv-modules.d/gconv-modules-extra.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;gconv-modules.d
&lt;span class="nb"&gt;cat &lt;/span&gt;gconv-modules-extra.conf | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'CN-?EXT'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; gconv-modules-extra-patched.conf
&lt;span class="nb"&gt;mv &lt;/span&gt;gconv-modules-extra-patched.conf gconv-modules-extra.conf
&lt;span class="nb"&gt;cd&lt;/span&gt; ..
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove the cache file if present:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm &lt;/span&gt;gconv-modules.cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You can regenerate that cache file using &lt;a href="https://www.man7.org/linux/man-pages/man8/iconvconfig.8.html" rel="noopener noreferrer"&gt;&lt;code&gt;iconvconfig(8)&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then re-check for the vulnerable encodings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iconv &lt;span class="nt"&gt;-l&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'CN-?EXT'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There should be no output from this command.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker
&lt;/h3&gt;

&lt;p&gt;For those using Docker images, here's a convenient &lt;code&gt;Dockerfile&lt;/code&gt; blurb:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Disable vulnerable iconv encodings (CVE-2024-2961)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.d &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cat &lt;/span&gt;gconv-modules-extra.conf | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'CN-?EXT'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; gconv-modules-extra-patched.conf &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mv &lt;/span&gt;gconv-modules-extra-patched.conf gconv-modules-extra.conf &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; ../gconv-modules.cache &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; iconvconfig &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; iconv &lt;span class="nt"&gt;-l&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'CN-?EXT'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line contains one of my favorite Dockerfile tricks (&lt;code&gt;check-something &amp;amp;&amp;amp; exit 1 || true&lt;/code&gt;) -- your Docker build will fail if the vulnerable charsets are enabled.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A previous version of this post kept &lt;code&gt;gconv-modules-extra-patched.conf&lt;/code&gt;. Thanks to Anonymous for pointing out that a subsequent RPM update could re-introduce the file.&lt;/p&gt;

&lt;p&gt;A previous version of this post indicated that &lt;code&gt;glibc&lt;/code&gt; versions &amp;lt; 2.39 were vulnerable. Thanks to Geert for noting that 2.39 is also vulnerable.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>linux</category>
      <category>php</category>
      <category>security</category>
    </item>
    <item>
      <title>Down the Rabbit Hole of Linux Terminal Emulators</title>
      <dc:creator>Garrett Mills</dc:creator>
      <pubDate>Fri, 05 May 2023 04:14:58 +0000</pubDate>
      <link>https://dev.to/garrettmills/down-the-rabbit-hole-of-linux-terminal-emulators-4kd3</link>
      <guid>https://dev.to/garrettmills/down-the-rabbit-hole-of-linux-terminal-emulators-4kd3</guid>
      <description>&lt;p&gt;For years now, I've used an excellent drop-down terminal emulator called &lt;a href="http://guake-project.org/index.html" rel="noopener noreferrer"&gt;Guake&lt;/a&gt; -- a GNOME port of Quake. It supported every feature I needed: Wayland (mostly), tabs, custom shortcuts, custom shell, transparency, and a toggle command (&lt;code&gt;guake -t&lt;/code&gt;) that plays nice w/ custom keybinds in GNOME.&lt;/p&gt;

&lt;p&gt;Recently, however, I've found myself wanting something akin to Vim's visual mode in Guake (specifically the ability to copy-paste chunks of output like file names into the command).&lt;/p&gt;

&lt;p&gt;While Guake doesn't support this, the &lt;a href="https://www.gnu.org/software/screen/" rel="noopener noreferrer"&gt;&lt;code&gt;screen&lt;/code&gt; command&lt;/a&gt; does:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Press &lt;code&gt;C-a [&lt;/code&gt; to enter copy mode.&lt;/li&gt;
&lt;li&gt;Move the cursor to the text you want to copy and press space.&lt;/li&gt;
&lt;li&gt;Highlight the text you want to copy and press space again to copy the selection and return to normal mode.&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;C-a ]&lt;/code&gt; to paste at the cursor.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is exactly what I wanted. Now, I just need to make &lt;code&gt;screen fish --login&lt;/code&gt; my default shell in Guake. This presented a couple issues.&lt;/p&gt;

&lt;p&gt;First, because I am using a GUI-based terminal emulator, I want to use its scroll-back functionality. By default, &lt;code&gt;screen&lt;/code&gt; buffers output and provides its own implementation of scroll-back.&lt;/p&gt;

&lt;p&gt;Luckily, this was easy enough to change with a line in my &lt;code&gt;~/.screenrc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;termcapinfo xterm*|xs|rxvt|terminal ti@:te@
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next problem was... not so easy to solve.&lt;/p&gt;

&lt;center&gt;
&lt;br&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fterminal-1.png" width="636" height="341"&gt;&lt;br&gt;
&lt;small&gt;Guake's preferences dialog.&lt;/small&gt;&lt;br&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;While Guake allows you to select a custom shell, it does &lt;em&gt;not&lt;/em&gt; allow you to specify a custom command. I need to do this because &lt;code&gt;screen&lt;/code&gt; does not register itself as a shell w/ the system.&lt;/p&gt;

&lt;p&gt;Sooooo using Guake is out of the question if I want to use my new Franken-screen shell setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  GNOME Terminator
&lt;/h2&gt;

&lt;center&gt;
&lt;br&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fterminal-2.png" width="800" height="416"&gt;&lt;br&gt;
&lt;small&gt;GNOME Terminator's preferences dialog.&lt;/small&gt;&lt;br&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;I tried several other shell emulators, but they all had game-breaking issues or missing functionality for me personally. Most notably, I actually got &lt;a href="https://gnome-terminator.org/" rel="noopener noreferrer"&gt;GNOME's Terminator&lt;/a&gt; tiling emulator completely set up to my liking. When I tried to replace my keyboard shortcut for Guake, thought, I discovered that there's no CLI flag to "toggle" the current window to make it behave like a drop-down shell.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tilix
&lt;/h2&gt;

&lt;center&gt;
&lt;br&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fterminal-3.png" width="800" height="289"&gt;&lt;br&gt;
&lt;small&gt;Tilix's Quake mode, Wayland edition.&lt;/small&gt;&lt;br&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;I also tried &lt;a href="https://gnunn1.github.io/tilix-web/" rel="noopener noreferrer"&gt;Tilix&lt;/a&gt;, another tiling emulator. Like Terminator, it had preferences for everything I like to customize in Guake -- it even has a Quake mode that makes it behave as a drop-down shell! Unlike Terminator, it has a CLI flag to show/hide the overhead window.&lt;/p&gt;

&lt;p&gt;Unfortunately, Tilix has limited support for Wayland in Quake mode. I was able to get it to work by updating my keyboard shortcut to use the &lt;code&gt;env GDK_BACKEND=x11&lt;/code&gt; environment.&lt;/p&gt;

&lt;center&gt;
&lt;br&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fterminal-4.png" width="517" height="87"&gt;&lt;br&gt;
&lt;small&gt;Ah, Wayland, how we love thee.&lt;/small&gt;&lt;br&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;This too has its own problem. One feature of Guake that I use a lot is the "Open on monitor with cursor" setting. This lets me quickly move Guake between monitors as I'm working.&lt;/p&gt;

&lt;p&gt;There is a &lt;a href="https://www.reddit.com/r/linux/comments/qid50x/does_anyone_else_feel_that_wayland_is_taking_away/" rel="noopener noreferrer"&gt;well-known limitation&lt;/a&gt; of Wayland where Wayland apps are restricted from accessing certain information about the displays and other running applications. This prevents Tilix from being able to determine which monitor the active cursor is on.&lt;/p&gt;

&lt;p&gt;This problem is not unique to Tilix. I use the excellent &lt;a href="https://ulauncher.io/" rel="noopener noreferrer"&gt;Ulauncher&lt;/a&gt; tool. On X.org, Ulauncher opens on the monitor where the cursor is. On Wayland, it picks the monitor seemingly at random.&lt;/p&gt;

&lt;p&gt;While searching around for this issue, I stumbled upon &lt;a href="https://github.com/gnunn1/tilix/issues/2049#issuecomment-1140499490" rel="noopener noreferrer"&gt;a comment&lt;/a&gt; on an issue in the Tilix GitHub which pointed me to a GNOME extension of all things called DDTerm.&lt;/p&gt;

&lt;h2&gt;
  
  
  DDTerm
&lt;/h2&gt;

&lt;center&gt;
&lt;br&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fterminal-5.png" width="583" height="300"&gt;&lt;br&gt;
&lt;small&gt;DDTerm and its glorious, glorious preference dialog.&lt;/small&gt;&lt;br&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;&lt;a href="https://github.com/ddterm/gnome-shell-extension-ddterm" rel="noopener noreferrer"&gt;DDTerm&lt;/a&gt; is "Another Drop Down Terminal Extension for GNOME Shell" and has managed to be a perfect drop-in replacement for Guake for me. It supports, among other things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A custom command to use for new sessions (&lt;code&gt;screen fish --login&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A DBus command to toggle a single instance (&lt;code&gt;gdbus call --session --dest org.gnome.Shell --object-path /org/gnome/Shell/Extensions/ddterm --method com.github.amezin.ddterm.Extension.Toggle&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Transparency&lt;/li&gt;
&lt;li&gt;Customizable keyboard shortcuts/color theme/fonts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plus, because it's running in GNOME directly, it can access the compositor to correctly determine the monitor where the mouse lives!&lt;/p&gt;

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

&lt;p&gt;This rabbit hole was probably 2 hours of on-again-off-again tinkering w/ various terminal emulators, but I have landed on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shell: Fish Shell wrapped in GNU Screen&lt;/li&gt;
&lt;li&gt;Emulator: DDTerm&lt;/li&gt;
&lt;li&gt;Custom &lt;code&gt;.screenrc&lt;/code&gt; to enable literal scrollback&lt;/li&gt;
&lt;li&gt;Custom GNOME keybinding to toggle DDTerm&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;This post originally appeared on my blog, &lt;a href="https://garrettmills.dev/blog/2023/04/28/down-the-rabbit-hole-of-linux-terminal-emulators/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>linux</category>
      <category>shell</category>
    </item>
    <item>
      <title>Adventures in AI-assisted proof generation</title>
      <dc:creator>Garrett Mills</dc:creator>
      <pubDate>Mon, 20 Mar 2023 12:00:00 +0000</pubDate>
      <link>https://dev.to/garrettmills/adventures-in-ai-assisted-proof-generation-ma7</link>
      <guid>https://dev.to/garrettmills/adventures-in-ai-assisted-proof-generation-ma7</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz49mkdrmb8qwm9y0s2wr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz49mkdrmb8qwm9y0s2wr.png" width="800" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look, I know LLMs and ChatGPT are the big industry-consuming buzz-words right now. I'm as skeptical of the "ChatGPT is going to replace &lt;code&gt;{industry}&lt;/code&gt;" mentality as anyone, but it's clear that &lt;em&gt;something&lt;/em&gt; is coming and it's going to change the world, whether we like it or not.&lt;/p&gt;

&lt;p&gt;Believe it or not, until a couple days ago, I had largely ignored the LLM craze. Sure, it's cool, and it's coming, but it's not relevant to what I do day-to-day &lt;em&gt;yet&lt;/em&gt;. Recently, however, I was reading the excellent &lt;a href="https://webcurios.co.uk/webcurios-17-03-23/" rel="noopener noreferrer"&gt;Web Curios blog by Matt Muir&lt;/a&gt;, and something he said stood out to me:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“But Matt!”, I hear you all cry, “what does this MEAN? Please tell me  what I should think about all this breathless, breakneck upheaval and  change and…is it…progress?” To which the obvious answer is “lol like I  know, I’m just some webmong” (and also “is it normal to hear these  voices?”), but to which I might also say “This is the point at which I  strongly believe it’s important you start to learn how to use this  stuff, because I reckon you’ve got maximum a year in which reasonable  competence with this sort of kit makes you look genuinely smart”, but  also “GPT4 is literally Wegovy for white collar office workers, insofar  as everyone will be using it to give themselves a professional tweakup  but people will be a bit cagey about admitting it”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, begrudgingly, I fired up OpenAI, handed over my e-mail and phone number, and started playing around with ChatGPT. I tried all the normal things ("generate a Bash script to do X", "write a new cold open for X show in the style of Y"), but I stumbled upon something interesting, rather by accident.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;del&gt;The&lt;/del&gt; A Problem With Coq
&lt;/h2&gt;

&lt;p&gt;(If you are familiar with Coq/provers, feel free to skip my basic and somewhat controversial explanation in this section.)&lt;/p&gt;

&lt;p&gt;When I was in school, one of my areas of focus was formal methods, a computing discipline that concerns itself with the rigorous theory of programming languages and programs. One of the major applications of FM is so-called "verified computing" -- computing which has been formally proven correct. Applications of verified computing are legion, including security-sensitive environments -- like embedded devices, drones, and spacecraft -- but have gradually extended to broader applications like secure boot and mainstream languages like TypeScript.&lt;/p&gt;

&lt;p&gt;The FM community has coalesced around a series of tools called interactive theorem provers which provide syntax for modeling theorems, composing them, and constructing proofs over them. The lab I worked with used one of the most popular, the &lt;a href="https://coq.inria.fr/" rel="noopener noreferrer"&gt;Coq Proof Assistant&lt;/a&gt;. Coq contains a rigid, functional programming language and a less-rigid proof scripting language which operates over it.&lt;/p&gt;

&lt;p&gt;Despite advocates claims that provers are &lt;a href="http://adam.chlipala.net/" rel="noopener noreferrer"&gt;"going mainstream any year now,"&lt;/a&gt; these proof assistants struggle in general -- and Coq in particular -- with a lack of automation. Quite extensive knowledge is required to prove even &lt;a href="https://stackoverflow.com/questions/48416538/writing-proofs-of-simple-arithmetic-in-coq" rel="noopener noreferrer"&gt;basic lemmas&lt;/a&gt;, largely due to the strict nature of the runtime and its unwillingness to make assumptions which may turn out to be logically inconsistent. Undoubtedly, this will improve with time as automations and layers of abstraction are introduced to the system, but it remains the largest barrier to entry for programmers wanting to write provably-correct code.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;del&gt;The&lt;/del&gt; A Problem with ChatGPT
&lt;/h2&gt;

&lt;p&gt;Of the many &lt;a href="https://www.forbes.com/sites/bernardmarr/2023/03/03/the-top-10-limitations-of-chatgpt" rel="noopener noreferrer"&gt;technical&lt;/a&gt;, &lt;a href="https://www.theguardian.com/technology/2023/mar/18/chatgpt-said-i-did-not-exist-how-artists-and-writers-are-fighting-back-against-ai" rel="noopener noreferrer"&gt;societal&lt;/a&gt;, and &lt;a href="https://www.scu.edu/ethics-spotlight/generative-ai-ethics/a-moral-panic-chatgpt-and-the-gamification-of-education/" rel="noopener noreferrer"&gt;moral&lt;/a&gt; quandaries spurred by ChatGPT is its complete willingness to fabricate lies disguised as the truth. Because all the LLM is doing is generating what it deems the statistically most likely response, it may be incorrect. However, its advanced language &lt;em&gt;syntax&lt;/em&gt; model enables it to present its incorrect information in an eloquent, tidy, convincing format (the boon of misinformation farms, but I digress...).&lt;/p&gt;

&lt;p&gt;Obviously proving whether or not the output of ChatGPT is correct &lt;em&gt;generally&lt;/em&gt; is an insane and likely impossible proposition, there's an interesting rabbit hole to be found on the boundary of AI-generated code snippets and Coq proofs. A wedding between the embodiment of industry buzzwords and something which is decidedly not. You see, it turns out that one of the niche languages that ChatGPT understands reasonably well is Ltac, the proof scripting language built into Coq.&lt;/p&gt;

&lt;h2&gt;
  
  
  Factorial
&lt;/h2&gt;

&lt;p&gt;A common baby's-second-proof in Coq is the factorial problem. Model the factorial function as a recursive function and an inductive relation, then prove that the two models are equivalent. So, I started by asking ChatGPT:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Show me an implementation of the factorial algorithm in Coq."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is what it came up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fixpoint factorial (n : nat) : nat :=
  match n with
  | O =&amp;gt; 1
  | S n' =&amp;gt; n * factorial n'
  end.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This compiled in Coq and, upon inspection, this appears correct. Impressive. Next, I asked it something a bit more challenging:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Now, show me an implementation of the factorial algorithm in Coq using an inductive relation instead of a recursive function."&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Inductive factorial : nat -&amp;gt; nat -&amp;gt; Prop :=
| fact_base : factorial O 1
| fact_step : forall n m, factorial n m -&amp;gt; factorial (S n) ((S n) * m).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This also compiled fine, and I think this is much more impressive. The LLM produced a reasonable model of a factorial system, "inferring" what I meant by "inductive relation." ChatGPT also helpfully provided an example lemma for this relation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Example factorial_5 : factorial 5 120.
Proof.
  apply (fact_step 4 24).
  apply (fact_step 3 6).
  apply (fact_step 2 2).
  apply (fact_step 1 1).
  apply fact_base.
Qed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where things started to get interesting to me. I didn't even ask for an example, but in its quest to explain itself, ChatGPT generated Ltac code demonstrating an example usage of its inductive relation. I was curious, so I chucked it into &lt;a href="https://github.com/whonore/Coqtail" rel="noopener noreferrer"&gt;Coqtail&lt;/a&gt; and tried it out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unable to unify "factorial 0 1" with "factorial 1 1".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hm. It looks like the ChatGPT proof missed a step between &lt;code&gt;apply (fact_step 1 1).&lt;/code&gt; and &lt;code&gt;apply fact_base.&lt;/code&gt; Adding in an &lt;code&gt;apply (fact_step O 1).&lt;/code&gt; between them pops it out.&lt;/p&gt;

&lt;p&gt;After my initial doubts in ChatGPT's "knowledge" of Coq, I started to get a little more ambitious:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Now, show me a proof that the inductive relation is equivalent to the recursive function definition."&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Lemma factorial_equiv : forall n, factorial n (factorial_rec n).
Proof.
  induction n.
  - apply fact_base.
  - simpl.
    apply (fact_step n (factorial_rec n)).
    apply IHn.
Qed.

Lemma factorial_rec_equiv : forall n, factorial_rec n = factorial n.
Proof.
  intros n.
  unfold factorial_rec.
  induction n.
  - reflexivity.
  - simpl.
    rewrite IHn.
    apply (fact_step n (factorial n)).
    apply factorial_equiv.
Qed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, that's something! There are so many interesting things about this response. First of all, it inferred that a "proof of equivalence" would require a lemma in both directions. Second of all, the Ltac it generated has &lt;em&gt;structure&lt;/em&gt; and is organized using bullets, not just a large blob of proof commands. Even more, it was able to account for the conflict between the recursive &lt;code&gt;factorial&lt;/code&gt; function it generated and the inductive &lt;code&gt;factorial&lt;/code&gt; relation it generated, renaming the recursive function &lt;code&gt;factorial_rec&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To my amazement, the &lt;code&gt;factorial_equiv&lt;/code&gt; popped out without any modifications, and the bullets structure even made sense. The &lt;code&gt;factorial_rec_equiv&lt;/code&gt; lemma however... has some problems. For one, neither &lt;code&gt;factorial_rec&lt;/code&gt; nor &lt;code&gt;factorial&lt;/code&gt; is single-parameter, so the lemma was missing an entire universal variable. I rewrote the theorem manually, to give the LLM a hint:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Modify the &lt;code&gt;factorial_req_equiv&lt;/code&gt; lemma to prove: &lt;code&gt;forall n m, factorial_rec n = m -&amp;gt; factorial n m"&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Lemma factorial_rec_equiv : forall n m, factorial_rec n = m -&amp;gt; factorial n m.
Proof.
  intros n m H.
  rewrite &amp;lt;- H.
  clear H.
  induction n.
  - apply fact_base.
  - simpl.
    apply (fact_step n (factorial_rec n)).
    apply IHn.
    reflexivity.
Qed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aside from the fact that the last &lt;code&gt;reflexivity&lt;/code&gt; is not needed, this theorem popped out perfectly. Now, I not only had an AI-generated factorial relation, but I had an AI-generated proof that that relation was correct (or at least equivalent to the recursive definition I defined as "correct").&lt;/p&gt;

&lt;p&gt;This is absolutely amazing to me, but of course there are caveats to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, this is an exceedingly simple example, and as you add libraries, lemmas, and other code it would become difficult to provide ChatGPT enough context for it to generate coherent results.&lt;/li&gt;
&lt;li&gt;Second, ChatGPT's generation of these outputs was &lt;em&gt;slow&lt;/em&gt;. So slow, in fact, that several times when I returned to the tab from something else, it had timed-out and needed to be retried. But, it did finish.&lt;/li&gt;
&lt;li&gt;Third, obviously my definition of "proven correctness" assumes logical consistency in the proof engine, but in this line of work that's a fair assumption to make.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Importantly, however, were I proving this for the first time, it would probably take &lt;em&gt;more&lt;/em&gt; time to work my way through the various proof tactics and commands.&lt;/p&gt;

&lt;p&gt;I think this has a few interesting implications: first, for theorem proving. The barrier to entry for theorem provers is largely an issue with how difficult it is to construct proof tactics. If ChatGPT generates a string of proof tactics which pops out a proof goal, then one of two things has happened. Either, (1) it has found a set of tactics which genuinely proves the statement, or (2) it has found a logical inconsistency -- a bug -- in the proof engine.&lt;/p&gt;

&lt;p&gt;The latter is the stuff of nightmares, and is generally not much of an issue in mature tools like Coq. The former, however, is really cool. Imagine a utopic world where you can hand a proof and a universe of statements to your theorem prover and, rather than requiring carefully constructed hints, &lt;a href="https://coq.inria.fr/refman/proofs/automatic-tactics/auto.html" rel="noopener noreferrer"&gt;the &lt;code&gt;auto&lt;/code&gt; tactic&lt;/a&gt; could use an LLM to generate the tactics for the proof. Even if it fails, it provides the user a much closer starting point than writing the proof from scratch.&lt;/p&gt;

&lt;p&gt;There's a much broader and more interesting implication here, though. One problem with ChatGPT-generated code is that it may simply be completely wrong. So, what if we make ChatGPT &lt;em&gt;prove&lt;/em&gt; that its code is correct? This, combined with Coq's &lt;a href="https://coq.inria.fr/refman/addendum/extraction.html" rel="noopener noreferrer"&gt;excellent extractors&lt;/a&gt;, could free up not just FM people, but even normal developers to create verified implementations while spending less time in the weeds with the prover. &lt;code&gt;auto&lt;/code&gt; on crack.&lt;/p&gt;

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

&lt;p&gt;I honestly don't know how I feel about ChatGPT in general, or even how it behaves in this particular situation. Maybe it simply does not scale to the complexity necessary to be useful. Maybe a rise in AI-generated Ltac will poison &lt;a href="https://joemorrison.medium.com/death-of-an-open-source-business-model-62bc227a7e9b" rel="noopener noreferrer"&gt;yet another vibrant FOSS community&lt;/a&gt;. It really feels like there's a social reckoning on the way.&lt;/p&gt;

&lt;p&gt;Regardless, it seems like ChatGPT's usefulness as a programming tool may extend -- just a bit -- to the world of verified programming.&lt;/p&gt;

&lt;p&gt;(I &lt;a href="https://code.garrettmills.dev/garrettmills/blog-chatgpt-coq" rel="noopener noreferrer"&gt;created a repo&lt;/a&gt; where you can find the generated Coq code as well as the output from my ChatGPT session.)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This post originally appeared on my blog, &lt;a href="https://garrettmills.dev/blog/2023/03/19/Adventures-in-AI-assisted-proof-generation/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>chatgpt</category>
      <category>coq</category>
      <category>programming</category>
    </item>
    <item>
      <title>Generalized Commutative Data-Types</title>
      <dc:creator>Garrett Mills</dc:creator>
      <pubDate>Fri, 09 Dec 2022 10:58:26 +0000</pubDate>
      <link>https://dev.to/garrettmills/generalized-commutative-data-types-195j</link>
      <guid>https://dev.to/garrettmills/generalized-commutative-data-types-195j</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Disclaimer: after I started writing about this, I found &lt;a href="https://hydro.run/papers/hydroflow-thesis.pdf" rel="noopener noreferrer"&gt;this paper from the Hydro project&lt;/a&gt; which presents a formulation of this idea using lattices &amp;amp; morphisms. What follows is my derivation of a similar technique, albeit significantly less formal. As far as I can tell, the Hydro paper does not separate "pseudo-commutative operations" instead opting to form reactive values which are re-computed as the PC operations are applied.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A "commutative data type" is one whose value is modified by a set of operations whose execution order is irrelevant. Such data types are useful in distributed &amp;amp; parallel systems which employ accumulator-style execution (i.e. many jobs perform a calculation then merge their result into a single, shared value).&lt;/p&gt;

&lt;p&gt;What follows is a formulation of such a data type along with the structures required to define various operations over it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Naïve Commutative Data Types: A First Draft
&lt;/h2&gt;

&lt;p&gt;Begin with a base value &lt;code&gt;v&lt;/code&gt; of some type &lt;code&gt;T&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are &lt;code&gt;n&lt;/code&gt; many jobs which act in parallel to perform commutative operations on &lt;code&gt;v&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;An operation on &lt;code&gt;v&lt;/code&gt; is commutative if, for all operations &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; of type &lt;code&gt;(T -&amp;gt; T)&lt;/code&gt;, &lt;code&gt;a (b v) = b (a v)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Each of the jobs produces an operation of the type &lt;code&gt;(T -&amp;gt; T)&lt;/code&gt; which are collected.&lt;/p&gt;

&lt;p&gt;The result is a value &lt;code&gt;(v : T)&lt;/code&gt; and a set of commutative operations &lt;code&gt;(list (T -&amp;gt; T))&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The list of operations is applied to the value, chained, producing a final value &lt;code&gt;v'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, if we have the list &lt;code&gt;((v -&amp;gt; v+1) :: (v -&amp;gt; v+4) :: nil)&lt;/code&gt; and a base value of &lt;code&gt;0&lt;/code&gt;, resolving the value gives &lt;code&gt;(v -&amp;gt; v+4) ((v -&amp;gt; v+1) 0) = 5&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Importantly, because the order of the operations is irrelevant, we can apply the operations as they are received by the reducer (the piece of software accumulating the result), rather than collecting them all at once.&lt;/p&gt;

&lt;p&gt;This allows for efficient reduction of a shared result variable by many distributed parallel jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pseudo-Commutative Operations
&lt;/h2&gt;

&lt;p&gt;Some operations, however, are not purely commutative. An example of this is multiplication.&lt;/p&gt;

&lt;p&gt;If we introduce a job which produces a multiply operation into the above example, the list of operations is no longer commutative (herein referred to as asymmetric, or inconsistent).&lt;/p&gt;

&lt;p&gt;However, the operation of multiplication is distributive in the sense that the TFAE:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;c * (a + b)&lt;/code&gt;&lt;br&gt;
&lt;code&gt;(c * a) + (c * b)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Or, perhaps more interestingly for our case, TFAE:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;d * (a + b + c)&lt;/code&gt;&lt;br&gt;
&lt;code&gt;(d * c) + (d * (a + b))&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For example, say we receive the following operations in the following order. A &lt;code&gt;C:&lt;/code&gt; prefix denotes a commutative operation, and a &lt;code&gt;P:&lt;/code&gt; prefix denotes a pseudo-commutative operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C: (v -&amp;gt; v + 1)
C: (v -&amp;gt; v + 2)
P: (v -&amp;gt; v * 2)
C: (v -&amp;gt; v + 3)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the base value of v is 0, we find that the "consistent" result should be &lt;code&gt;(0 + 1 + 2 + 3) * 2 = 12&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(v -&amp;gt; v + 1) 0  =&amp;gt; 1
(v -&amp;gt; v + 2) 1  =&amp;gt; 3
(v -&amp;gt; v * 2) 3  =&amp;gt; 6

(v -&amp;gt; v + 3) 6  =&amp;gt; 9   (incorrect)
(v -&amp;gt; (v + 3) * 2) 6  =&amp;gt; 18   (incorrect)
(v -&amp;gt; v + (3 * 2)) 6  =&amp;gt; 12   (incorrect)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the commutative operation is opaque, there is no way of "pushing" the pseudo-commutative operation into the subsequent commutative operations, resulting in asymmetric results.&lt;/p&gt;

&lt;p&gt;To address this, we re-define our naïve commutative operations like so:&lt;/p&gt;

&lt;p&gt;A commutative operation is pair of the form &lt;code&gt;(T, T -&amp;gt; T -&amp;gt; T)&lt;/code&gt; where the first element is the right-operand to a commutative binary operation. The second element is a function which takes the current accumulated value and the right operand and returns the new accumulated value.&lt;/p&gt;

&lt;p&gt;This structure removes the opacity of the right operand in the operation, allowing us to push the pseudo-commutative operation into subsequent commutative operations.&lt;/p&gt;

&lt;p&gt;We similarly re-define PC operations to have the form &lt;code&gt;(T, T -&amp;gt; T -&amp;gt; T)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, the same example using the new structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C: (1, l -&amp;gt; r -&amp;gt; l + r)
C: (2, l -&amp;gt; r -&amp;gt; l + r)
P: (2, l -&amp;gt; r -&amp;gt; l * r)
C: (3, l -&amp;gt; r -&amp;gt; l + r)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This results in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(l -&amp;gt; r -&amp;gt; l + r) 0 1  =&amp;gt; 1
(l -&amp;gt; r -&amp;gt; l + r) 1 2  =&amp;gt; 3
(l -&amp;gt; r -&amp;gt; l * r) 3 2  =&amp;gt; 6
(l -&amp;gt; r -&amp;gt; l + r) 6 ((l -&amp;gt; r -&amp;gt; l * r) 3 2)  =&amp;gt; 12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the fundamental insight of pseudo-commutative operations: if they are folded into the operands of all subsequent operations applied to the accumulator, the ordering of commutative and pseudo-commutative operations is irrelevant (insofar as the correct pseudo-commutative folds are performed).&lt;/p&gt;

&lt;p&gt;Pseudo-commutative operations can even be chained to arrive at similarly-consistent results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C: (2, l -&amp;gt; r -&amp;gt; l + r)
P: (2, l -&amp;gt; r -&amp;gt; l * r)
P: (3, l -&amp;gt; r -&amp;gt; l * r)
C: (2, l -&amp;gt; r -&amp;gt; l + r)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected result here is &lt;code&gt;(0 + 2 + 2) * 2 * 3 = 24&lt;/code&gt;, and is computed as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(l -&amp;gt; r -&amp;gt; l + r) 0 2  =&amp;gt; 2
(l -&amp;gt; r -&amp;gt; l * r) 2 2  =&amp;gt; 4
(l -&amp;gt; r -&amp;gt; l * r) 4 3  =&amp;gt; 12
(l -&amp;gt; r -&amp;gt; l + r) 12 ((l -&amp;gt; r -&amp;gt; l * r) ((l -&amp;gt; r -&amp;gt; l * r) 2 2) 3)  =&amp;gt; 24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another added benefit of this representation is the lack of specialization of the operations. Both commutative and pseudo-commutative operations can be represented as generic functions over two parameters, and those functions reused for each operation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Another GCDT: Sets
&lt;/h3&gt;

&lt;p&gt;We will further formulate a GCDT over sets. A value of type &lt;code&gt;(set T)&lt;/code&gt; is a collection of distinct, non-ordered values of type &lt;code&gt;T&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A set has a characteristic commutative operation: append (or, more generally, union). Because sets have no order, the order in which unions are applied is irrelevant.&lt;/p&gt;

&lt;p&gt;We use the &lt;code&gt;∪&lt;/code&gt; operator to represent set union. So, &lt;code&gt;A ∪ B&lt;/code&gt; is the union of sets &lt;code&gt;A&lt;/code&gt; and &lt;code&gt;B&lt;/code&gt;. For unions, the right operand is clear.&lt;/p&gt;

&lt;p&gt;An operation, therefore, may be something like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;(B, l -&amp;gt; r -&amp;gt; l ∪ r)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Sets also have a clear pseudo-commutative operation: map (or set comprehension, if you prefer). This is the operation of applying a function &lt;code&gt;(T1 -&amp;gt; T2)&lt;/code&gt; to every element in a set, resulting in a set of type &lt;code&gt;set T2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We represent set comprehension with the &lt;code&gt;map&lt;/code&gt; function, which is of the form: &lt;code&gt;map :: (T1 -&amp;gt; T2) -&amp;gt; set T1 -&amp;gt; set T2&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Here's an example, assuming we start with a base value &lt;code&gt;v = {}&lt;/code&gt; (the empty set):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C: ({1}, l -&amp;gt; r -&amp;gt; l ∪ R)
C: ({1, 2}, l -&amp;gt; r -&amp;gt; l ∪ R)
P: ({}, l -&amp;gt; _ -&amp;gt; map (* 2) l)
C: ({3, 4}, l -&amp;gt; r -&amp;gt; l ∪ r)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Interestingly, &lt;code&gt;map&lt;/code&gt; is a pseudo-commutative operation, but it is unary. To fit the structure, we implement it as a binary operation, but ignore the right operand, since it is always the one specified by the PC operation itself.&lt;/p&gt;

&lt;p&gt;The expected result here is map &lt;code&gt;(* 2) ({1} U {1, 2} U {3, 4}) = {2, 4, 6, 8}&lt;/code&gt;, and is computed as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(l -&amp;gt; r -&amp;gt; l ∪ r) {} {1}  =&amp;gt; {1}
(l -&amp;gt; r -&amp;gt; l ∪ r) {1} {1, 2}  =&amp;gt; {1, 2}
(l -&amp;gt; _ -&amp;gt; map (* 2) l) {1, 2} {}  =&amp;gt; {2, 4}
(l -&amp;gt; r -&amp;gt; l ∪ r) {2, 4} ((l -&amp;gt; _ -&amp;gt; map (* 2) l) {3, 4} {})  =&amp;gt; {2, 4, 6, 8}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pseudo-Commutative Operation Precedence
&lt;/h2&gt;

&lt;p&gt;Now, let's introduce another pseudo-commutative operation over sets: filter (or set subtraction). Set subtraction removes all elements in the right operand from the left operand. For example, &lt;code&gt;{1, 2, 3} - {2} = {1, 3}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This can similarly be implemented using a function of type &lt;code&gt;(T -&amp;gt; bool)&lt;/code&gt; which removes an element from &lt;code&gt;set T&lt;/code&gt; unless the function returns true.&lt;/p&gt;

&lt;p&gt;Based on the properties defined above, we can apply set subtraction in an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C: ({1, 4, 7}, l -&amp;gt; r -&amp;gt; l ∪ r)
P: ({}, l -&amp;gt; _ -&amp;gt; filter (&amp;lt; 5) l)
C: ({2, 5, 8}, l -&amp;gt; r -&amp;gt; l ∪ r)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected result here is filter &lt;code&gt;(&amp;lt; 5) ({1, 4, 7} U {2, 5, 8}) = {5, 7, 8}&lt;/code&gt;, and is computed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(l -&amp;gt; r -&amp;gt; l ∪ r) {} {1, 4, 7}  =&amp;gt; {1, 4, 7}
(l -&amp;gt; _ -&amp;gt; filter (&amp;lt; 5) l) {1, 4, 7} {}  =&amp;gt; {7}
(l -&amp;gt; r -&amp;gt; l ∪ r) {7} (filter (&amp;lt; 5) {2, 5, 8})  =&amp;gt; {5, 7, 8}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Something problematic happens when we combine the two pseudo-commutative operators, however:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C: ({1, 4, 7}, l -&amp;gt; r -&amp;gt; l ∪ r)
P: ({}, l -&amp;gt; _ -&amp;gt; filter (&amp;lt; 5) l)
P: ({}, l -&amp;gt; _ -&amp;gt; map (* 2) l)
C: ({2, 5, 8}, l -&amp;gt; r -&amp;gt; l ∪ r)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on whether we filter then map or map then filter, we arrive at &lt;code&gt;{10, 14, 16}&lt;/code&gt; or &lt;code&gt;{8, 10, 14, 16}&lt;/code&gt;, an asymmetric result. Unlike commutative operations, pseudo-commutative operations are not necessarily commutative &lt;em&gt;with each other&lt;/em&gt;. Thus, the order in which pseudo-commutative operations are applies matters a great deal.&lt;/p&gt;

&lt;p&gt;To resolve this inconsistency, we can require pseudo-commutative operations to be orderable such that, for a set of pseudo-commutative operations &lt;code&gt;s1&lt;/code&gt;, there exists a list of these operations &lt;code&gt;s2&lt;/code&gt; such that, &lt;code&gt;s2&lt;/code&gt; has the form &lt;code&gt;{ s_i | s_i in s1 and forall j &amp;lt; i, s_i &amp;gt; s_j }&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This gives precedence to pseudo-commutative operations, allowing their order to be resolved when they are "pushed" into subsequent commutative operands, but how do we handle the case when a greater PC operation is received &lt;em&gt;after&lt;/em&gt; a lesser PC operation is processed?&lt;/p&gt;

&lt;p&gt;One approach to this is to specify the inverse of an operation, allowing it to be efficiently re-ordered.&lt;/p&gt;

&lt;p&gt;For example, say we have an initial value &lt;code&gt;v0&lt;/code&gt; and a PC operation &lt;code&gt;({}, pc1, pc1')&lt;/code&gt; (where &lt;code&gt;pc1'&lt;/code&gt; inverts &lt;code&gt;pc1&lt;/code&gt;). If a subsequent PC operation with a greater precedence is applied, &lt;code&gt;({}, pc2, pc2')&lt;/code&gt;, we compute the accumulator like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v = v0
v = pc1 v {}
v = pc1 (pc2 (pc1' v {}) {}) {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach has a few benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;First, by inverting and re-applying operations on-the-fly, we avoid the need to re-compute the accumulator all the way from the initial value. Instead, we only need to re-compute the operations which were PC and of a lower priority.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Second, because of this rewinding approach, you will never have to rewind a PC operation of equal or greater precedence, as the operations of lesser precedence will always be "closest" to the end of the chain.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, commutative operations need not be re-applied during a rewind. Instead, the resultant value is   treated as a pre-existing member of the set to be re-computed, since the commuted operation is preserved through the inverse of the PC operations.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, there are a few drawbacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Depending on the order in which the PC operations are received, the reducer may be forced to perform unoptimally-many re-computation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fundamentally, some PC operations will lack easily computable inverses. For example &lt;code&gt;map sqrt&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This last case is perhaps the most serious drawback to this approach, but it also has a fairly simple solution.&lt;/p&gt;

&lt;p&gt;Because the entire domain of a PC operation is known when the operation is applied, we can trivially define an inversion of the operation by building a map from the range -&amp;gt; domain and storing that after the PC is applied (we call this "auto-inversion").&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This will require updating the mapping as the PC is applied to subsequent commutative operations, but such updates are considered relatively minor overhead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This allows us to auto-invert any PC operation. The trade-off here is between time and space complexity.&lt;/p&gt;

&lt;p&gt;In cases where the domain operand is small, but the inverse operation complex or impossible to define, defining the inverse as a mapping is more efficient.&lt;/p&gt;

&lt;p&gt;However, in cases where the domain operand is large, the resultant auto-inverse may require a large amount of memory. In these cases, if the inverse operation is efficiently computable, defining an inverse function is more efficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applications
&lt;/h2&gt;

&lt;p&gt;The motivation for this thought exercise came from &lt;a href="https://github.com/swarmlang" rel="noopener noreferrer"&gt;Swarm&lt;/a&gt;: a modular &amp;amp; massively-parallel distributed programming language I've been building w/ &lt;a href="https://github.com/ephing" rel="noopener noreferrer"&gt;Ethan Grantz&lt;/a&gt; for the past year.&lt;/p&gt;

&lt;p&gt;Swarm provides set-enumeration constructs which are natively parallelized and shared variables whose synchronization is handled by the runtime.&lt;/p&gt;

&lt;p&gt;However, the language still relies on the developer to avoid asymmetric operations. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;enumeration&amp;lt;number&amp;gt; e = [1, 2, 3, 4, 5];
shared number acc = 0;

enumerate e as n {
    if ( n % 2 == 0 ) {
        acc += n;
    } else {
        acc *= n;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example is somewhat contrived, but it is easy to see that the order in which the &lt;code&gt;enumerate&lt;/code&gt; body executes for each element of &lt;code&gt;e&lt;/code&gt; determines the value of &lt;code&gt;acc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This example could be made consistent by treating &lt;code&gt;acc&lt;/code&gt; as the initial value of a GCDT of type &lt;code&gt;number&lt;/code&gt;, and each execution of the body would submit one of two operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- If n % 2 == 0:
C: (n, l -&amp;gt; r -&amp;gt; l + r)

-- Else:
P: (n, l -&amp;gt; r -&amp;gt; l * r)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, using the method described above, this result is always consistent, regardless of the order in which the jobs are executed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This post originally appeared on my blog, &lt;a href="https://garrettmills.dev/blog/2022/12/09/Generalized-Commutative-Data-Types/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>parallel</category>
      <category>programming</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>Rancher K3s: Kubernetes on Proxmox Containers</title>
      <dc:creator>Garrett Mills</dc:creator>
      <pubDate>Tue, 19 Apr 2022 05:54:40 +0000</pubDate>
      <link>https://dev.to/garrettmills/rancher-k3s-kubernetes-on-proxmox-containers-54mp</link>
      <guid>https://dev.to/garrettmills/rancher-k3s-kubernetes-on-proxmox-containers-54mp</guid>
      <description>&lt;p&gt;For a long time now, I've self-hosted most of my online services like calendar, contacts, e-mail, cloud file storage, my website, &amp;amp;c. The current iteration of my setup relies on a series of Ansible playbooks that install all of the various applications and configure them for use.&lt;/p&gt;

&lt;p&gt;This has been really stable, and has worked pretty well for me. I deploy the applications to a set of LXC containers (read: &lt;a href="https://linuxcontainers.org/lxd/" rel="noopener noreferrer"&gt;lightweight Linux VMs&lt;/a&gt;) on &lt;a href="https://www.proxmox.com/en/" rel="noopener noreferrer"&gt;Proxmox&lt;/a&gt;, a free and open-source hypervisor with an excellent management interface.&lt;/p&gt;

&lt;p&gt;Recently, however, I've been re-learning Docker and the benefits of deploying applications using containers. Some of the big ones are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Guaranteed, reproducible environments. The application ships with its dependencies, ready to run.&lt;/li&gt;
&lt;li&gt;Portability. Assuming your environment supports the container runtime, it supports the application.&lt;/li&gt;
&lt;li&gt;Infrastructure-as-code. Much like Ansible playbooks, Docker lends itself well to managing the container environment using code, which can be tracked and versioned.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, I have decided to embark on the journey of transitioning my bare-Linux Ansible playbooks to a set of Kubernetes deployments.&lt;/p&gt;

&lt;p&gt;However, there are still some things I like about Proxmox that I'm not willing to give up. For one, the ability to virtualize physical machines (like my router or access point management portal) that can't be easily containerized. Having the ability to migrate "physical" OS installs between servers when I need to do maintenance on the hosts is super useful.&lt;/p&gt;

&lt;p&gt;So, I will be installing Kubernetes on Proxmox, and I want to do it on LXC containers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We're Building &amp;amp; Rationale
&lt;/h2&gt;

&lt;p&gt;I'm going to deploy a Kubernetes cluster using &lt;a href="https://rancher.com/docs/k3s/latest/en/" rel="noopener noreferrer"&gt;Rancher's K3s&lt;/a&gt; distribution on top of LXC containers.&lt;/p&gt;

&lt;p&gt;K3s is a lightweight, production-grade Kubernetes distribution that simplifies the setup process by coming pre-configured with DNS, networking, and other tools out of the box. K3s also makes it fairly painless to join new workers to the cluster. This, combined with the relatively small scale of my deployment, makes it a pretty easy choice.&lt;/p&gt;

&lt;p&gt;LXC containers, on the other hand, might seem a bit of an odd choice. Nearly every other article I found deploying K8s on Proxmox did so using full-fat virtual machines, rather than containers. This is certainly the lower-friction route, since it's procedurally the same as installing it on physical hosts. I went with LXC containers for two main reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;LXC containers are &lt;em&gt;fast&lt;/em&gt;. Like, almost as fast as bare metal.&lt;/strong&gt; Because LXC containers are virtualized at the kernel level, they are much lighter than traditional VMs. As such, they boot nearly instantly, run at nearly the same speed as the host kernel, and are much easier to reconfigure with more RAM/disk space/CPU cores on the fly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LXC containers are smaller.&lt;/strong&gt; Because the containers run on the kernel of the host, they need to contain a much smaller set of packages. This makes them require much less disk space out of the box (and, therefore, makes them easier to migrate).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, to start out, I'm going to create 2 containers: one control node, and one worker node.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;I'm going to assume that you (1) have a Proxmox server up and running, (2) have a &lt;a href="https://us.lxd.images.canonical.com/images/rockylinux/8/amd64/default/" rel="noopener noreferrer"&gt;container template&lt;/a&gt; available on Proxmox, and (3) you have some kind of NFS file server.&lt;/p&gt;

&lt;p&gt;This last one is important since we'll be giving our containers a relatively small amount of disk space. So, any volumes needed by Kubernetes pods can be created as &lt;a href="https://kubernetes.io/docs/concepts/storage/volumes/#nfs" rel="noopener noreferrer"&gt;NFS mounts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You'll also want to set up &lt;code&gt;kubectl&lt;/code&gt; and &lt;code&gt;helm&lt;/code&gt; tools on your local machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the LXC Containers
&lt;/h2&gt;

&lt;p&gt;Because our LXC containers need to be able to run Docker containers themselves, we need to do a bit of additional configuration out of the box to give them proper permissions.&lt;/p&gt;

&lt;p&gt;The process for setting up the 2 containers is pretty much identical, so I'm only going to go through it once.&lt;/p&gt;

&lt;p&gt;In the Proxmox UI, click "Create CT." Make sure you check the box to show advanced settings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fct-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fct-1.png" width="778" height="574"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;center&gt;&lt;small&gt;Make sure to uncheck "Unprivileged container."&lt;/small&gt;&lt;/center&gt;
&lt;br&gt;

&lt;p&gt;Fill in the details of the container. Make sure to uncheck the "Unprivileged container" checkbox. On the next screen, select your template of choice. I'm using a &lt;a href="https://us.lxd.images.canonical.com/images/rockylinux/8/amd64/default/" rel="noopener noreferrer"&gt;Rocky Linux 8 image&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fct-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fct-2.png" width="781" height="573"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I elected to give each container a root disk size of 16 GiB, which is more than enough for the OS and K3s to run, as long as we don't put any volumes on the disk itself.&lt;/p&gt;

&lt;p&gt;The CPU and Memory values are really up to whatever you have available on the host, and the workloads you intend to run on your K8s cluster. For mine, I gave 4 vCPU cores and 4 GiB of RAM per container.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fct-3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fct-3.png" width="785" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the network configuration, be sure to set a static IP address for each node. Additionally, if you use a specific internal DNS server (which I highly recommend!), you should configure that on the next page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fct-4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fct-4.png" width="793" height="586"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, on the last page, make sure to uncheck the "Start after created" checkbox and then click finish. Proxmox will create the container.&lt;/p&gt;
&lt;h3&gt;
  
  
  Additional Configuration
&lt;/h3&gt;

&lt;p&gt;Now, we need to tweak a few things under-the-hood to give our containers proper permissions. You'll need to SSH into your Proxmox host as the &lt;code&gt;root&lt;/code&gt; user to run these commands.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;/etc/pve/lxc&lt;/code&gt; directory, you'll find files called &lt;code&gt;XXX.conf&lt;/code&gt;, where &lt;code&gt;XXX&lt;/code&gt; are the ID numbers of the containers we just created. Using your text editor of choice, edit the files for the containers we created to add the following lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc.apparmor.profile: unconfined
lxc.cgroup.devices.allow: a
lxc.cap.drop:
lxc.mount.auto: "proc:rw sys:rw"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: It's important that the container is stopped when you try to edit the file, otherwise Proxmox's network filesystem will prevent you from saving it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In order, these options (1) disable &lt;a href="https://www.apparmor.net/" rel="noopener noreferrer"&gt;AppArmor&lt;/a&gt;, (2) allow the container's cgroup to access all devices, (3) prevent dropping any capabilities for the container, and (4) mount &lt;code&gt;/proc&lt;/code&gt; and &lt;code&gt;/sys&lt;/code&gt; as read-write in the container.&lt;/p&gt;

&lt;p&gt;Next, we need to publish the kernel boot configuration into the container. Normally, this isn't needed by the container since it runs using the host's kernel, but the Kubelet uses the configuration to determine various settings for the runtime, so we need to copy it into the container. To do this, first start the container using the Proxmox web UI, then run the following command on the Proxmox host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pct push &amp;lt;container &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /boot/config-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; /boot/config-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, &lt;strong&gt;in each of the containers&lt;/strong&gt;, we need to make sure that &lt;code&gt;/dev/kmsg&lt;/code&gt; exists. Kubelet uses this for some logging functions, and it doesn't exist in the containers by default. For our purposes, we'll just alias it to &lt;code&gt;/dev/console&lt;/code&gt;. In each container, create the file &lt;code&gt;/usr/local/bin/conf-kmsg.sh&lt;/code&gt; with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh -e&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; /dev/kmsg &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /dev/console /dev/kmsg
&lt;span class="k"&gt;fi

&lt;/span&gt;mount &lt;span class="nt"&gt;--make-rshared&lt;/span&gt; /
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script symlinks &lt;code&gt;/dev/console&lt;/code&gt; as &lt;code&gt;/dev/kmsg&lt;/code&gt; if the latter does not exist. Finally, we will configure it to run when the container starts with a SystemD one-shot service. Create the file &lt;code&gt;/etc/systemd/system/conf-kmsg.service&lt;/code&gt; with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Unit]
Description=Make sure /dev/kmsg exists

[Service]
Type=simple
RemainAfterExit=yes
ExecStart=/usr/local/bin/conf-kmsg.sh
TimeoutStartSec=0

[Install]
WantedBy=default.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, enable the service by running the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /usr/local/bin/conf-kmsg.sh
systemctl daemon-reload
systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; conf-kmsg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting Up the Container OS &amp;amp; K3s
&lt;/h2&gt;

&lt;p&gt;Now that we've got the containers up and running, we will set up Rancher K3s on them. Luckily, Rancher intentionally makes this pretty easy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup the control node
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Starting on the control node&lt;/strong&gt;, we'll run the following command to setup K3s:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsL&lt;/span&gt; https://get.k3s.io | sh &lt;span class="nt"&gt;-s&lt;/span&gt; - &lt;span class="nt"&gt;--disable&lt;/span&gt; traefik &lt;span class="nt"&gt;--node-name&lt;/span&gt; control.k8s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;K3s ships with a &lt;a href="https://rancher.com/docs/k3s/latest/en/networking/#traefik-ingress-controller" rel="noopener noreferrer"&gt;Traefik ingress controller&lt;/a&gt; by default. This works fine, but I prefer to use the industry-standard &lt;a href="https://kubernetes.github.io/ingress-nginx/" rel="noopener noreferrer"&gt;NGINX ingress controller&lt;/a&gt; instead, so we'll set that up manually.&lt;/li&gt;
&lt;li&gt;I've specified the node name manually using the &lt;code&gt;--node-name&lt;/code&gt; flag. This may not be necessary, but I've had problems in the past with K3s doing a reverse-lookup of the hostname from the IP address, resulting in different node names between cluster restarts. Specifying the name explicitly avoids that issue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If all goes well, you should see an output similar to:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fct-5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fct-5.png" width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once this is done, you can copy the &lt;code&gt;/etc/rancher/k3s/k3s.yaml&lt;/code&gt; as &lt;code&gt;~/.kube/config&lt;/code&gt; on your local machine and you should be able to see your new (admittedly single node) cluster using &lt;code&gt;kubectl get nodes&lt;/code&gt;!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: you may need to adjust the cluster address in the config file from &lt;code&gt;127.0.0.1&lt;/code&gt; to the actual IP/domain name of your control node.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fct-6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fct-6.png" width="674" height="103"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup the worker node
&lt;/h3&gt;

&lt;p&gt;Now, we need to join our worker node to the K3s cluster. This is also pretty straightforward, but you'll need the cluster token in order to join the node.&lt;/p&gt;

&lt;p&gt;You can find this by running the following command &lt;strong&gt;on the control node&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /var/lib/rancher/k3s/server/node-token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, &lt;strong&gt;on the worker node&lt;/strong&gt; run the following command to set up K3s and join the existing cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsL&lt;/span&gt; https://get.k3s.io | &lt;span class="nv"&gt;K3S_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://&amp;lt;control node ip&amp;gt;:6443 &lt;span class="nv"&gt;K3S_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;cluster token&amp;gt; sh &lt;span class="nt"&gt;-s&lt;/span&gt; - &lt;span class="nt"&gt;--node-name&lt;/span&gt; worker-1.k8s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, note that we specified the node name explicitly. Once this process finishes, you should now see the worker node appear in &lt;code&gt;kubectl get nodes&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fct-7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fct-7.png" width="657" height="125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can repeat this process for any additional worker nodes you want to join to the cluster in the future.&lt;/p&gt;

&lt;p&gt;At this point, we have a functional Kubernetes cluster, however because we disabled Traefik, it has no ingress controller. So, let's set that up now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up NGINX Ingress Controller
&lt;/h2&gt;

&lt;p&gt;I used the &lt;code&gt;ingress-nginx/ingress-nginx&lt;/code&gt; Helm chart to set up the NGINX ingress controller. To do this, we'll add the repo, load the repo's metadata, then install the chart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm &lt;span class="nb"&gt;install &lt;/span&gt;nginx-ingress ingress-nginx/ingress-nginx &lt;span class="nt"&gt;--set&lt;/span&gt; controller.publishService.enabled&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the &lt;code&gt;controller.publishService.enabled&lt;/code&gt; setting tells the controller to publish the ingress service IP addresses to the ingress resources.&lt;/p&gt;

&lt;p&gt;After the chart completes, you should see the various resources appear in &lt;code&gt;kubectl get all&lt;/code&gt; output. (Note that it may take a couple minutes for the controller to come online and assign IP addresses to the load balancer.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fct-8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fct-8.png" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can test that the controller is up and running by navigating to any of the node's addresses in a web browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fct-9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fct-9.png" width="800" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, we expect to see the 404, since we haven't configured any services to ingress through NGINX. The important thing is that we got a page served by NGINX.&lt;/p&gt;

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

&lt;p&gt;Now, we have a fully-functional Rancher K3s Kubernetes cluster, and the NGINX Ingress Controller configured and ready to use.&lt;/p&gt;

&lt;p&gt;I've found this cluster to be really easy to maintain and scale. If you need to add more nodes, just spin up another LXC container (possibly on another physical host, possibly not) and just repeat the section to join the worker to the cluster.&lt;/p&gt;

&lt;p&gt;I'm planning to do a few more write-ups chronicling my journey to learn and transition to Kubernetes, so stay tuned for more like this. The next step in this process is to configure &lt;a href="https://cert-manager.io/" rel="noopener noreferrer"&gt;cert-manager&lt;/a&gt; to automatically generate Let's Encrypt SSL certificates and deploy a simple application to our cluster.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This post originally appeared on my blog, &lt;a href="https://garrettmills.dev/blog/2022/04/18/Rancher-K3s-Kubernetes-on-Proxmox-Container/" rel="noopener noreferrer"&gt;here.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>docker</category>
      <category>kubernetes</category>
      <category>proxmox</category>
      <category>devops</category>
    </item>
    <item>
      <title>Runtime Data Validation from TypeScript Interfaces</title>
      <dc:creator>Garrett Mills</dc:creator>
      <pubDate>Fri, 14 Jan 2022 21:03:38 +0000</pubDate>
      <link>https://dev.to/garrettmills/runtime-data-validation-from-typescript-interfaces-3j8d</link>
      <guid>https://dev.to/garrettmills/runtime-data-validation-from-typescript-interfaces-3j8d</guid>
      <description>&lt;p&gt;For the last year or so, I've been (slowly) building a TypeScript-based Node.js framework called &lt;a href="https://extollo.garrettmills.dev/" rel="noopener noreferrer"&gt;Extollo&lt;/a&gt;. One of the design goals with Extollo is to only expose the user (i.e. the developer) to ES/TypeScript native concepts, in order to reduce the amount of special knowledge required to get up and running with the framework.&lt;/p&gt;

&lt;h1&gt;
  
  
  Runtime schemata: a plague of DSLs
&lt;/h1&gt;

&lt;p&gt;One of my biggest pet-peeves with the current Node.js framework scene is that nearly every ecosystem has to re-invent the wheel when it comes to schema definitions. Because JavaScript doesn't have a native runtime type-specification system (at least, not a good one), if you want to encode details about how a data structure should look at runtime, you need to design a system for passing that information along at runtime.&lt;/p&gt;

&lt;p&gt;For example, a prolific MongoDB ODM for Node.js, Mongoose, gives users the ability to specify the schema of the records in the collection when the user defines a model. Here's a sample schema definition from the &lt;a href="https://mongoosejs.com/docs/guide.html#definition" rel="noopener noreferrer"&gt;Mongoose docs&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;mongoose&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mongoose&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mongoose&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blogSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// String is shorthand for {type: String}&lt;/span&gt;
  &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;votes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;favs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="nb"&gt;Number&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm currently building the request validation system for Extollo. Because it has to process web requests with dynamic input, the validator interfaces need to be specified at runtime, so they data can be checked against the schema. To do this, I'm using the fantastic &lt;a href="https://github.com/colinhacks/zod" rel="noopener noreferrer"&gt;Zod schema validator library&lt;/a&gt; written by Colin McDonnell.&lt;/p&gt;

&lt;p&gt;However, Zod falls victim to the same fundamental problem with runtime schemata in JavaScript as Mongoose. Because its schemata need to be available at runtime, you have to use Zod's custom schema builder to define your interfaces. Here's an example of a schema for some data that might come from a login page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LoginAttemptSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;nonempty&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;nonempty&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;rememberMe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not too bad, but it does require the developer to learn Zod's specific schema definition language. I find this especially annoying since TypeScript already &lt;em&gt;has&lt;/em&gt; an interface definition language! This is a situation where I'd like to avoid making the developer learn an equivalent system if they already know the one built into the language.&lt;/p&gt;

&lt;p&gt;Let's rewrite this schema in TypeScript for a start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;LoginAttempt&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/** @minLength 1 */&lt;/span&gt;
    &lt;span class="nl"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;

    &lt;span class="cm"&gt;/** @minLength 1 */&lt;/span&gt;
    &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;

    &lt;span class="nx"&gt;rememberMe&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okay, that's an improvement! We can use TypeScript's native type syntax to define the interface, and augment it with JSDoc comments for any properties that can't be natively expressed. So, to use this with Zod, we need to convert it from the TypeScript syntax to the Zod syntax. Luckily, Fabien Bernard has spearheaded the excellent &lt;a href="https://github.com/fabien0102/ts-to-zod" rel="noopener noreferrer"&gt;ts-to-zod project&lt;/a&gt;, which looks through interfaces defined in a file and outputs the equivalent Zod schemata for them.&lt;/p&gt;

&lt;p&gt;Hmm.. so now the user can write their schema definitions in (mostly) native TypeScript syntax, and, with a bit of helper tooling, we can convert them to the Zod format so we can use them at runtime. Perfect! Well, almost...&lt;/p&gt;

&lt;p&gt;We have a subtle problem that arises when we want to actually _use _a schema at runtime. Let's look at an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Validator&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@extollo/lib&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LoginAttempt&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../types/LoginAttempt.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getValidator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Validator&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LoginAttempt&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This class has a method which returns a new Validator instance with the LoginAttempt schema as its type-parameter. Intuitively, this should produce a validator which, at runtime, validates data against the LoginAttempt schema. Let's look at the compiled JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use strict&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;__esModule&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Validator_1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@extollo/lib&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;Validator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;getValidator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Validator_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Validator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uh, oh. Ignoring the boilerplate noise, we see that our nice, type-parameterized Validator instance has been stripped of its type information. Why? TypeScript is a &lt;a href="https://devopedia.org/transpiler" rel="noopener noreferrer"&gt;transpiler&lt;/a&gt;. So, it takes TypeScript code and outputs the _equivalent _JavaScript code. Because JavaScript has no concept of types at runtime, the transpiler (in this case, tsc) strips them out.&lt;/p&gt;

&lt;p&gt;So now we have a problem. We've improved our user-interface by only requiring the developer to specify the TypeScript types, but now we can't &lt;em&gt;use&lt;/em&gt; them at runtime, because the TypeScript types get stripped away. 'What about the Zod schema we just generated?' you ask, wisely. Well, unfortunately, there's no mapping between the interface and the Zod schema it induced, and there's no easy way to create such a mapping, because it has to be done at compile-time.&lt;/p&gt;

&lt;h1&gt;
  
  
  A very deep rabbit-hole
&lt;/h1&gt;

&lt;p&gt;Ordinarily, this is where the story ends. You need some kind of mapping between the interface and the Zod schema (which, remember, the developer has no idea exists thanks to our ts-to-zod magic) to make the Validator work. In a generic TypeScript project, you'd have to have some kind of naming convention, or expose the schema to the user somehow to create the mapping.&lt;/p&gt;

&lt;p&gt;However, Extollo has a unique advantage that I suspected could be used to solve this problem transparently: &lt;a href="https://code.garrettmills.dev/extollo/cc" rel="noopener noreferrer"&gt;excc&lt;/a&gt;. Extollo projects are primarily TypeScript projects, but they also contain other files like views, assets, &amp;amp;c. that need to be included in the built-out bundle. To standardize all of this, Extollo uses its own project-compiler called &lt;code&gt;excc&lt;/code&gt; for builds. &lt;code&gt;excc&lt;/code&gt; is primarily &lt;a href="https://code.garrettmills.dev/Extollo/cc/src/branch/master/src/phases/CompilePhase.ts#L20" rel="noopener noreferrer"&gt;a wrapper around tsc&lt;/a&gt; that does some additional pre- and post-processing to handle the cases above.&lt;/p&gt;

&lt;p&gt;Because Extollo projects are all using &lt;code&gt;excc&lt;/code&gt;, this means that we can do arbitrary processing at compile time. I suspected that there would be a way to create a mapping between the interfaces and the schemata we generate for runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zod-ifying the Interfaces
&lt;/h2&gt;

&lt;p&gt;The first step was converting the TypeScript interfaces to Zod schemata using ts-to-zod. In &lt;code&gt;excc&lt;/code&gt;, this is implemented as &lt;a href="https://code.garrettmills.dev/Extollo/cc/src/branch/master/src/phases/ZodifyPhase.ts#L30" rel="noopener noreferrer"&gt;a pre-processing step&lt;/a&gt; that appends the Zod schema to the .ts file that contains the interface. So, the processed LoginAttempt.ts might look something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;LoginAttempt&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/** @minLength 1 */&lt;/span&gt;
    &lt;span class="nl"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;

    &lt;span class="cm"&gt;/** @minLength 1 */&lt;/span&gt;
    &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;

    &lt;span class="nx"&gt;rememberMe&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exZodifiedSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;nonempty&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;nonempty&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;rememberMe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This has some drawbacks. Namely, it assumes that only one interface is defined per-file. However, Extollo enforces this convention for other concepts like models, middleware, controllers, and config files, so it's fine to make that assumption here.&lt;/p&gt;

&lt;p&gt;This gets us closer, but it still doesn't do the mapping for the runtime schema. The first step to this is going to be devising some way of referencing a schema so that we can easily modify the TypeScript code that uses its related interface.&lt;/p&gt;

&lt;p&gt;I don't love the initial system I have for this, but what &lt;code&gt;excc&lt;/code&gt; does now is generate a unique ID number for each interface it Zod-ifies. Then, when it is writing the Zod schema into the interface's file, it adds code to register it with &lt;a href="https://code.garrettmills.dev/extollo/lib/src/branch/master/src/validation/ZodifyRegistrar.ts" rel="noopener noreferrer"&gt;a global service&lt;/a&gt; that maps the ID number to the Zod schema at runtime. So, the above file would actually look something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;registerZodifiedSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@extollo/lib&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;LoginAttempt&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/** @minLength 1 */&lt;/span&gt;
    &lt;span class="nl"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;

    &lt;span class="cm"&gt;/** @minLength 1 */&lt;/span&gt;
    &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;

    &lt;span class="nx"&gt;rememberMe&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/** @ex-zod-id 11@ */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exZodifiedSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;nonempty&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;nonempty&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;rememberMe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;registerZodifiedSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;exZodifiedSchema&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This may not seem like much, but this is a &lt;em&gt;huge&lt;/em&gt; step toward our goal. We now have, at compile time, a mapping of interfaces to IDs and, at runtime, a mapping of IDs to schemata. So, we can use the compile-time map to modify all the places that reference the interface to set a runtime parameter with the ID of the schema for that interface. Then, at runtime, we can look up the schema using the ID. Bingo! No, how do we actually do that...&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrangling the AST
&lt;/h2&gt;

&lt;p&gt;Now that we have our mapping, we need to make sure that a look-up is done whenever the type is referenced in code. That is, anywhere where we create a Validator&amp;lt;LoginAttempt&amp;gt;, we should set the ID of the Zod schema for LoginAttempt on that Validator instance.&lt;/p&gt;

&lt;p&gt;To accomplish this, I wrote a couple of transformer plugins for TypeScript. Now, tsc doesn't support plugins by default. (You may have seen plugins in the tsconfig.json for a project, but they are &lt;a href="https://www.typescriptlang.org/tsconfig#plugins" rel="noopener noreferrer"&gt;plugins for the editor's language server&lt;/a&gt;, not the compiler.) Luckily for us, again, there exists a fantastic open-source package to solve this problem. Ron S. maintains a package called &lt;a href="https://github.com/nonara/ts-patch" rel="noopener noreferrer"&gt;ts-patch&lt;/a&gt; which, aptly, patches the tsc installation for a project to allow the project to specify compiler-plugins.&lt;/p&gt;

&lt;p&gt;These plugins operate on the abstract syntax-tree of the TypeScript program. If you're not familiar with ASTs, they're basically the compiler's &lt;a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree" rel="noopener noreferrer"&gt;internal representation of the program&lt;/a&gt; you're compiling. They are data structures which can be manipulated and optimized. When you install a plugin, it is called repeatedly with the AST for each source file in the TypeScript project you're compiling. Importantly, the plugin can replace any of the nodes in the AST, or return a completely different one, in the file, and tsc will output the modified version instead of the original.&lt;/p&gt;

&lt;h3&gt;
  
  
  First, Identify
&lt;/h3&gt;

&lt;p&gt;The first plugin operates on the entire AST for each file in the project. Its job is to walk through each file's AST and &lt;a href="https://code.garrettmills.dev/Extollo/cc/src/branch/master/src/transformer.ts#L92" rel="noopener noreferrer"&gt;look for interface declarations&lt;/a&gt; that we generated Zod schema for. When it finds one, it parses out the ID number we wrote into the file earlier and stores a mapping between that ID number and the symbol TypeScript uses to identify the interface internally.&lt;/p&gt;

&lt;p&gt;Because we were the ones that wrote the Zod schema into the file, we can know that it -- and the call to register it with the global service -- are the last statements in the file. So, we can quickly look them up and parse out the ID from the registration call.&lt;/p&gt;

&lt;p&gt;The TypeScript AST for the augmented file, at this point, looks something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbpj25gm8ft89puiu54iv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbpj25gm8ft89puiu54iv.png" width="800" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(As an aside, I used the ts-ast-viewer web app to generate &lt;a href="https://ts-ast-viewer.com/#code/JYWwDg9gTgLgBAbzgLzgXzgMyhEcBEyEAJvgNwBQoksicUApgObADOMDUAWicJsA2IBlAMYALBiACG6LDjz4AAgwAeMCABsNEAPQbgAI3IUKqmvGAA7DlExSRDOABkILSwEEYHcPAQUAkDoAVEFwiiBWTgyWTDBicACMcEE6AQCurJyWUiAMAFxw7FBWTAEBwaHhkdGx8UkpAWBSrKwA7tDEBUUlZf6MuSAGnACyDAD8BQYQmgxSlhRoJhVhqgC0RMSrwMSJCYrJqWbQ8CIQluxwqjzEfALC4pIyALwoAHQQBgBWDCIwABR+fwZLI5fJvboxP4ASleljOkjAMAAntCADSNZptDoFZCvCFMaGw+E+FFQ9F9SSSIZQUY415TGZzQkQRHAM5SDRohZQyiMFjsTjXW6CUQSaR-BIJVGXFRC-gih7SHkUIA" rel="noopener noreferrer"&gt;this hierarchy&lt;/a&gt;. ts-ast-viewer is &lt;a href="https://github.com/dsherret/ts-ast-viewer" rel="noopener noreferrer"&gt;a project started by David Sherret&lt;/a&gt; that allows you to visualize and explore the AST for any TypeScript program. It was invaluable in helping me figure out the structures for this project.)&lt;/p&gt;

&lt;p&gt;By recursively walking the AST, we can look for the InterfaceDeclaration nodes. If we find one in a file, we can check the root of the file to see if an Identifier called exZodifiedSchema is defined. If so, we grab the last statement in the file (an ExpressionStatement containing the call to registerZodifiedSchema) and pull out its first argument, the ID number.&lt;/p&gt;

&lt;p&gt;Once this transformer finishes, we've identified all of the interfaces for which we generated Zod schemata and created a mapping from the interface to the ID number we need at runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Then, Modify
&lt;/h3&gt;

&lt;p&gt;The second plugin runs after the first has finished going through all the files in the project. This plugin's job is to &lt;a href="https://code.garrettmills.dev/Extollo/cc/src/branch/master/src/transformer.ts#L7" rel="noopener noreferrer"&gt;replace any NewExpression nodes&lt;/a&gt; where the type parameters contain Zod-ified interfaces with an &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/IIFE" rel="noopener noreferrer"&gt;IIFE&lt;/a&gt; that sets the __exZodifiedSchemata property to an array of the ID numbers used to look up the schemata for those interfaces.&lt;/p&gt;

&lt;p&gt;That is, the plugin transforms this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Validator&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LoginAttempt&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;into this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Validator&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LoginAttempt&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;vI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__exZodifiedSchemata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;vI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And because the NewExpression is an expression just like the CallExpression is, anywhere where we have a NewExpression can instead have this CallExpression that wraps it with additional logic. The transformer is able to look up the ID numbers associated with the interfaces because the Identifier that references the interface in &lt;code&gt;new Validator&amp;lt;LoginAttempt&amp;gt;()&lt;/code&gt; has the same symbol set on it as the InterfaceDeclaration we identified with the first plugin.&lt;/p&gt;

&lt;p&gt;These symbols are created by something in the TypeScript compiler called the linker, which resolves all the identifier declarations and matches them up with the uses of those identifiers, even if the actual variables have been renamed along the way. So, we can use these symbols to match up uses of the interface with the declarations of the interfaces we care about.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This&lt;/em&gt; is the magic sauce that finally makes it all work. After this plugin runs, the program TypeScript finishes compiling has all of the runtime type mappings linked up to the Validator instances based on which interface was specified when the Validator was instantiated.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;This was a long, and kind of hacky journey, but the end result is excellent. From the developer's perspective, they can type this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Validator&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LoginAttempt&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and, at runtime, the validator instance will have a Zod schema and will be able to parse data against the schema. No custom schema definition languages, no validator/schema mapping, nothing. To the developer, it's all just pure TypeScript, which was the goal all along.&lt;/p&gt;

&lt;p&gt;The code for this feature is still very much work-in-progress, and I have to remove a lot of unused code and clean up what I keep, and probably rewrite part of it to be a less... jank. But, at the end of the day, I'm really happy with this "magic" feature that will help keep Extollo projects TypeScript-native, and easy to maintain.&lt;/p&gt;

&lt;p&gt;You can find a minimal working example matching this post &lt;a href="https://code.garrettmills.dev/garrettmills/blog-runtime-types" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This post originally appeared on my blog, &lt;a href="https://garrettmills.dev/blog/2022/01/14/Runtime-Data-Validation-from-TypeScript-Interfaces/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>extollo</category>
    </item>
    <item>
      <title>Rethinking Dependency Injection in TypeScript</title>
      <dc:creator>Garrett Mills</dc:creator>
      <pubDate>Tue, 30 Mar 2021 19:06:41 +0000</pubDate>
      <link>https://dev.to/garrettmills/rethinking-dependency-injection-in-typescript-37pd</link>
      <guid>https://dev.to/garrettmills/rethinking-dependency-injection-in-typescript-37pd</guid>
      <description>&lt;center&gt;
    &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fts_di_header.png" width="572" height="248"&gt;
&lt;/center&gt;

&lt;blockquote&gt;
&lt;p&gt;This post originally appeared on my blog, &lt;a href="https://garrettmills.dev/blog/2021/03/30/Rethinking-Dependency-Injection-in-TypeScript/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Anyone who has read my blog before knows that I have a particular interest in dependency injection and inversion-of-control paradigms.&lt;/p&gt;

&lt;p&gt;Over the last few years, I've implemented DI in JavaScript for various projects, and I'm currently in the process of rewriting my framework and its DI implementation, so I wanted to share some observations about different JavaScript/TypeScript DI strategies.&lt;/p&gt;

&lt;p&gt;In particular, we'll explore named-injection, constructor-injection, and property-injection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Named Injection
&lt;/h2&gt;

&lt;p&gt;My &lt;a href="https://garrettmills.dev/blog/2019/11/16/Dependency-Injection-in-Less-Than-100-Lines-of-Pure-JavaScript/" rel="noopener noreferrer"&gt;first foray into DI in JavaScript&lt;/a&gt; relied on purely-runtime code and allowed injecting services from a container by name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Injectable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Injectable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SomeInjectableClass&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Injectable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;services&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;logging&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="nf"&gt;myMethod&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myMethod called!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was a fairly efficient and scalable paradigm, and defining the services as a property on the class itself made it easy to account for the services required by parent classes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AnotherInjectableClass&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;SomeInjectableClass&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;services&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;another_service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;myMethod&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;another_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;something&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;myMethod&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In fact, this mechanism was reliable enough that it became the basis of the injector used in my &lt;a href="https://code.garrettmills.dev/Flitter/di" rel="noopener noreferrer"&gt;Flitter framework&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Drawbacks
&lt;/h3&gt;

&lt;p&gt;This method is not without its downsides, however. For one, all classes must extend a common &lt;code&gt;Injectable&lt;/code&gt; base class. If your class extends from, say, a base class from a library, then it can't be injected directly.&lt;/p&gt;

&lt;p&gt;Likewise, relying on service names makes it hard to know exactly what's being injected into your class. Especially as I am transitioning more projects and my framework over to TypeScript, relying on named-injection just wasn't going to cut it. This would require referencing properties with the &lt;code&gt;any&lt;/code&gt;  type annotation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SomeInjectableClass&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Injectable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;services&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;another_service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;myMethod&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;another_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;something&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;// NOT type safe&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Relying on named services also makes the injectable classes inflexible, as the services have to be injected into properties with the same name. Say, for example, I have a service called &lt;code&gt;models&lt;/code&gt;, and a class that uses it. If that class wants to keep an array called &lt;code&gt;models&lt;/code&gt;, it will conflict with the injected service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SomethingThatUsesModels&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Injectable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;services&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;models&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// CONFLICT with the injected 'models' service&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because a named-injector would have to bypass type-safety, this could lead to a situation where the TypeScript compiler types &lt;code&gt;models&lt;/code&gt; as &lt;code&gt;Model[]&lt;/code&gt;, but the injector overrides it to be the injected &lt;code&gt;models&lt;/code&gt; service, which would cause runtime errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Constructor Injection
&lt;/h2&gt;

&lt;p&gt;Since we're working in TypeScript, we want to do away with named-injection entirely. The TypeScript compiler has a flag which, when enabled, emits the type metadata for classes and properties, making it available via the Reflection API.&lt;/p&gt;

&lt;p&gt;This is useful because it effectively enables "naming" a dependency based on its type, rather than an arbitrary string. So, when defining typed injectable classes, each property contains &lt;em&gt;two&lt;/em&gt; pieces of information, rather than just one.&lt;/p&gt;

&lt;p&gt;Likewise, we can enable the experimental "decorators" functionality, which can allow us inject any arbitrary class rather than requiring it to extend a base &lt;code&gt;Injectable&lt;/code&gt; class. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SomethingThatUsesModels&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;modelsService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ModelsService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anyone who has used the Angular framework is familiar with this format. The Angular DI historically worked this way, using type reflection to handle injection. Nowadays, it uses its custom compiler to handle injection at compile time, but that's beyond the scope of this writeup.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does this work?
&lt;/h3&gt;

&lt;p&gt;Okay, so we have a decorator and some type annotations. But, how do we actually do the injection from that?&lt;/p&gt;

&lt;p&gt;The key is that &lt;code&gt;Injectable&lt;/code&gt; decorator. In essence, this decorator is a function that accepts the class it decorates. Then, this function uses the &lt;code&gt;reflect-metadata&lt;/code&gt; package to get a list of type annotations for the constructor's parameters, then stores that information as additional metadata.&lt;/p&gt;

&lt;p&gt;Here's a (simplified) example from the &lt;a href="https://code.garrettmills.dev/extollo/di/src/branch/master/src/decorator/injection.ts" rel="noopener noreferrer"&gt;Extollo DI&lt;/a&gt; (Flitter's TypeScript successor):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Get a collection of dependency requirements for the given target object.
 * @param {Object} target
 * @return Collection&amp;lt;DependencyRequirement&amp;gt;
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;initDependencyMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Collection&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DependencyRequirement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paramTypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;design:paramtypes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DependencyKey&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paramTypes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DependencyRequirement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;paramIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;overridden&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Class decorator that marks a class as injectable. When this is applied, dependency
 * metadata for the constructors params is resolved and stored in metadata.
 * @constructor
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Injectable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;ClassDecorator&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;initDependencyMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DEPENDENCY_KEYS_METADATA_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In essence, all this decorator does is read the type annotations from the class' meta-data and store them in a nicer format in its own meta-data key (&lt;code&gt;DEPENDENCY_KEYS_METADATA_KEY&lt;/code&gt;).&lt;/p&gt;

&lt;h4&gt;
  
  
  Instantiating the Class
&lt;/h4&gt;

&lt;p&gt;Okay, so we have the type annotations stored in meta-data, but how do we actually inject them into the class? This is where the container comes in.&lt;/p&gt;

&lt;p&gt;In our old paradigm, the container was a class that mapped service names (&lt;code&gt;another_service&lt;/code&gt;) to factories that created the service with that name. (e.g. &lt;code&gt;another_service&lt;/code&gt; to &lt;code&gt;instanceof AnotherService&lt;/code&gt;). In the type-based system, the container is a class that maps &lt;em&gt;types&lt;/em&gt; to factories that create the service with that type.&lt;/p&gt;

&lt;p&gt;This result is very strong as it enables type-safe injection. In the example above, the "token", &lt;code&gt;ModelsService&lt;/code&gt; is mapped to an instance of the &lt;code&gt;ModelsService&lt;/code&gt; by the container.&lt;/p&gt;

&lt;p&gt;So, when we ask the container to inject and create an instance of our &lt;code&gt;SomethingThatUsesModels&lt;/code&gt; class, the container goes through all the items in the &lt;code&gt;DEPENDENCY_KEYS_METADATA_KEY&lt;/code&gt; meta-data key and resolves them. Then, it passes those instances into the new class to instantiate it. For a (simplified) example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;resolveAndCreate&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Instantiable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DEPENDENCY_KEYS_METADATA_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orderByAsc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paramIndex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dependency&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolveAndCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dependency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;token&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, we can instantiate our &lt;code&gt;SomethingThatUsesModels&lt;/code&gt; class like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SomethingThatUsesModels&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolveAndCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SomethingThatUsesModels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Drawbacks
&lt;/h3&gt;

&lt;p&gt;The constructor-injection paradigm works well and addresses many of the features we cared about between named-injection. In particular:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provides type-hinted injection&lt;/li&gt;
&lt;li&gt;Separates class property names from injection tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, one way this falls behind named-injection is in the sense that the child classes must know and provide the dependencies of their parents.&lt;/p&gt;

&lt;p&gt;For example, assume I have a class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParentClass&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nx"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoggingService&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, I want to define a child of this class that has its own dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChildClass&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ParentClass&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nx"&gt;another&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AnotherService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// ERROR!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will immediately fail to compile, since the &lt;code&gt;ChildClass&lt;/code&gt; doesn't pass the required dependencies into the parent. In reality, the child class must &lt;em&gt;also&lt;/em&gt; specify the dependencies of the parent as parameters in its constructor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChildClass&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ParentClass&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nx"&gt;another&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AnotherService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoggingService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The issue with this becomes immediately obvious. All of the dependencies and imports of the parent must also be specified in &lt;em&gt;all&lt;/em&gt; of the children. As the classes become larger and the inheritance chain becomes longer, you can quickly run into ridiculously long constructor signatures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LargerControllerClass&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ParentControllerClass&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nx"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoggingService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ConfigService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ModelsService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SocketService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ViewRenderer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;other&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OtherService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;another&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AnotherService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;more&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MoreService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;other&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;another&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;more&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, not only does the child need to be aware of the dependencies of the parent, it needs to take into account the order of the constructor parameters, which might be irrelevant in practice, but could break between versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Property Injection
&lt;/h2&gt;

&lt;p&gt;To improve upon this, we want to divorce the injected dependencies from the constructor while still maintaining the type-hinted and property-name benefits we gained from constructor-injection&lt;/p&gt;

&lt;p&gt;This has the additional benefit of keeping the constructor signatures smaller, and keeping the non-injected constructor parameters distinct from the injected ones.&lt;/p&gt;

&lt;p&gt;Luckily, in TypeScript, properties of a class also emit type annotations, and can be decorated. So, we can change our&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ParentClass&lt;/code&gt; and &lt;code&gt;ChildClass&lt;/code&gt;  definitions to look as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParentClass&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Inject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoggingService&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChildClass&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ParentClass&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Inject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;another&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AnotherService&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How does this work?
&lt;/h3&gt;

&lt;p&gt;The "magic" bit here is the &lt;code&gt;@Inject()&lt;/code&gt; decorator, which looks at the type annotation of the property it decorates and stores that property and its token value as meta-data on the class. Here's a simplified example of Extollo's &lt;a href="https://code.garrettmills.dev/extollo/di/src/branch/master/src/decorator/injection.ts#L69" rel="noopener noreferrer"&gt;implementation&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Mark the given class property to be injected by the container.
 * @constructor
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Inject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;PropertyDecorator&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;propertyMetadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Collection&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PropertyDependency&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DEPENDENCY_KEYS_PROPERTY_METADATA_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;propertyMetadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;design:type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;propertyMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;firstWhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;property&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;propertyMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DEPENDENCY_KEYS_PROPERTY_METADATA_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;propertyMetadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when the container creates an instance of a class, instead of passing in the dependencies as parameters to the constructor, it instantiates the class, then sets the properties on the class that have &lt;code&gt;@Inject()&lt;/code&gt;  decorators. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;resolveAndCreate&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Instantiable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;token&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DEPENDENCY_KEYS_PROPERTY_METADATA_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;instances&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolveAndCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;inst&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a problem here, though. Say we were to &lt;code&gt;resolveAndCreate&amp;lt;ChildClass&amp;gt;(ChildClass)&lt;/code&gt;. Because of the way JavaScript works, the instance returned by this call would ONLY have the properties defined in the child class, not the parent (i.e. &lt;code&gt;another&lt;/code&gt;, but not &lt;code&gt;logging&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;To understand why, we need a bit of background.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Prototype Chain
&lt;/h4&gt;

&lt;p&gt;In JavaScript, inheritance is prototypical. Say we have the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ParentClass&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ChildClass&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The object created as &lt;code&gt;parent&lt;/code&gt; has a "chain" of prototypes that comprise it. So, if I try to access a method or property on &lt;code&gt;parent&lt;/code&gt;, JavaScript will first check if the property exists on &lt;code&gt;parent&lt;/code&gt; itself. If not, it will check if the property exists on &lt;code&gt;ParentClass.prototype&lt;/code&gt;, then &lt;code&gt;ParentClass.prototype.prototype&lt;/code&gt;, and so on.&lt;/p&gt;

&lt;p&gt;If you follow the prototype chain long enough, every item in JavaScript eventually extends from &lt;code&gt;Object.prototype&lt;/code&gt; or &lt;code&gt;Function.prototype&lt;/code&gt;. (For classes, it's the latter.) From any of these prototypes, we can access the constructor they are associated with using &lt;code&gt;Class.prototype.constructor&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So, to get the &lt;code&gt;ParentClass&lt;/code&gt; constructor from its prototype, we could do &lt;code&gt;ParentClass.prototype.constructor&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Issue
&lt;/h4&gt;

&lt;p&gt;When our &lt;code&gt;@Inject()&lt;/code&gt; decorator saves the meta-data about the property type annotations, it does so by defining a new meta-data property on the prototype of the class where the property was defined.&lt;/p&gt;

&lt;p&gt;Since the &lt;code&gt;logging&lt;/code&gt; property was first defined and decorated in the &lt;code&gt;ParentClass&lt;/code&gt;, the meta-data property with the information we need is actually defined on &lt;code&gt;ParentClass.prototype&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, when &lt;code&gt;@Inject()&lt;/code&gt; is called for the &lt;code&gt;another&lt;/code&gt; property in the &lt;code&gt;ChildClass&lt;/code&gt;, it &lt;em&gt;defines&lt;/em&gt; a new meta-data key with &lt;code&gt;ChildClass&lt;/code&gt;'s defined properties on the &lt;code&gt;ChildClass.prototype&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Thus, in order to get all the properties we need to inject, we must check the meta-data defined for &lt;em&gt;all&lt;/em&gt; prototypes in the inheritance chain of the constructor being instantiated. So, the container implementation might look something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;resolveAndCreate&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Instantiable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;token&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Collection&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PropertyDependency&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;currentToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;

        &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loadedMeta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DEPENDENCY_KEYS_PROPERTY_METADATA_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;loadedMeta&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loadedMeta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nx"&gt;currentToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nb"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;
            &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolveAndCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;inst&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, &lt;code&gt;inst&lt;/code&gt; will have all properties defined as injected for all parent classes in the inheritance chain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best of Both Worlds
&lt;/h3&gt;

&lt;p&gt;This approach combines the benefits of named-injection with the type-safety of constructor-injection:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Child classes don't need to account for the dependencies of their parents&lt;/li&gt;
&lt;li&gt;Injected dependencies can be type-hinted&lt;/li&gt;
&lt;li&gt;Property names of dependencies are independent of their types&lt;/li&gt;
&lt;li&gt;Parent dependencies are automatically accounted for&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After using it for a while, I really like this paradigm. It provides a type-safe way to do dependency injection reliably, while still keeping the class definitions clean and parent-agnostic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Drawbacks
&lt;/h3&gt;

&lt;p&gt;While its still my preferred solution, property-injection in TypeScript still isn't without its drawbacks. Namely, it requires use of the &lt;a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator" rel="noopener noreferrer"&gt;non-null assertion operator&lt;/a&gt; since the properties are being filled in by the container.&lt;/p&gt;

&lt;p&gt;Because of this, if you were to instantiate a class manually outside the container and not fill in all the properties, the compiler wouldn't catch it. Accessing properties on that instance would lead to runtime errors. However, assuming you always instantiate &lt;code&gt;Injectable&lt;/code&gt; classes with the container, this problem is largely mute.&lt;/p&gt;

&lt;p&gt;Another downside that I didn't explore much in this article is the container code. Generalizing the container (for either constructor- or property-injection) requires use of the &lt;code&gt;any&lt;/code&gt; operator &lt;em&gt;at some point&lt;/em&gt; since factories are matched by key. At least in my implementation. I'd be interested to see alternatives.&lt;/p&gt;

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

&lt;p&gt;There will, undoubtedly, be another iteration of this article wherein I discover a new paradigm I want to try. But for the foreseeable future, I'll be implementing and running with property-injection in my projects. As I mentioned in the article, constructor-injection and property-injection support form the basis of the dependency injector for the &lt;a href="https://extollo.garrettmills.dev" rel="noopener noreferrer"&gt;Extollo framework&lt;/a&gt;, my new project.&lt;/p&gt;

&lt;p&gt;I tried not to dive too deep into the actual code required to implement the various strategies in this article, so if you're interested in seeing how I've implemented them for my projects, here are some links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://code.garrettmills.dev/flitter/di" rel="noopener noreferrer"&gt;The Flitter DI&lt;/a&gt; (named-injection)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.garrettmills.dev/extollo/di" rel="noopener noreferrer"&gt;The Extollo DI&lt;/a&gt; (constructor- and property-injection, WIP)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As always, I'd love to hear any other strategies or ways people have implemented this, so feel free to leave a comment or &lt;a href="https://garrettmills.dev/#contact" rel="noopener noreferrer"&gt;get in touch&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Converting an Ionic/Angular Site into a Progressive Web App</title>
      <dc:creator>Garrett Mills</dc:creator>
      <pubDate>Tue, 01 Dec 2020 18:00:05 +0000</pubDate>
      <link>https://dev.to/garrettmills/converting-an-ionic-angular-site-into-a-progressive-web-app-2c6j</link>
      <guid>https://dev.to/garrettmills/converting-an-ionic-angular-site-into-a-progressive-web-app-2c6j</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post originally appeared on my blog, &lt;a href="https://garrettmills.dev/blog/2020/12/01/Converting-Ionic-Angular-Site-into-PWA/" rel="noopener noreferrer"&gt;here.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For the past year, I've been working on a web application called &lt;a href="https://code.garrettmills.dev/Noded" rel="noopener noreferrer"&gt;Noded&lt;/a&gt;. Noded is built in Angular on the Ionic framework and provides tools for building a personal tree of information. (If you're curious, you can try it out &lt;a href="https://noded.garrettmills.dev/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.)&lt;/p&gt;

&lt;center&gt;
    &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fpwa-1.png" width="800" height="415"&gt;
    &lt;small&gt;A screenshot from Noded.&lt;/small&gt;
&lt;/center&gt;

&lt;p&gt;Because Noded is meant to replace whatever note-taking application a person uses, it's important that it be available offline (on your phone, for instance). So, one of the goals for Noded was to make it work as a progressive web app so it could be loaded even when the client doesn't have Internet access.&lt;/p&gt;

&lt;p&gt;For the uninitiated, a progressive web app (or PWA) is a type of web app that can make use of native-integration features like push notifications, storage, &amp;amp;c. On mobile platforms, this also enables &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Add_to_home_screen" rel="noopener noreferrer"&gt;the "Add to Home Screen" functionality&lt;/a&gt; which enables users to "install" a PWA to their device so it appears as a native application and opens in full-screen mode, rather than in a browser.&lt;/p&gt;

&lt;center&gt;
    &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fpwa-2.png" width="800" height="1422"&gt;
    &lt;br&gt;
    &lt;small&gt;Noded, running as a PWA on my phone.&lt;/small&gt;
&lt;/center&gt;

&lt;h2&gt;
  
  
  Service Workers
&lt;/h2&gt;

&lt;p&gt;In order for a web app to become a PWA, it needs two things. First, it needs a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Manifest" rel="noopener noreferrer"&gt;web manifest&lt;/a&gt;, which tells the browser the location of all resources used by the web app, and other information like the icon and background color. Second, it needs to have a service worker registered. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API" rel="noopener noreferrer"&gt;Service workers&lt;/a&gt; are event-based JavaScript programs that run in the background on a user's browser.&lt;/p&gt;

&lt;p&gt;These background programs can run even when the app itself isn't open and enable things like offline mode and push notifications. Ever wonder how applications like Google Docs can still load even when the browser is offline? This is enabled by the service worker API.&lt;/p&gt;

&lt;p&gt;Your application's service worker sits like a layer between your application and its back-end server. When your app makes a request to the server, it is intercepted by the service worker which decides whether it will be forwarded to the back-end, or retrieved from the local cache.&lt;/p&gt;

&lt;p&gt;PWAs work offline by having the service worker cache all of their app resources offline automatically. Then, when the back-end server is unreachable, the resources are served from the service worker transparently to the application. Even when your app is online, service workers can dramatically speed up load times for people with slow or latent connections (especially those in developing areas).&lt;/p&gt;

&lt;h2&gt;
  
  
  Angular Service Worker
&lt;/h2&gt;

&lt;p&gt;Because of their structured nature, Angular apps can make use of the &lt;a href="https://angular.io/guide/service-worker-intro" rel="noopener noreferrer"&gt;Angular Service Worker&lt;/a&gt; which can automatically integrate with Angular apps to cache the built modules offline. This can be much easier to configure than writing a service-worker from scratch.&lt;/p&gt;

&lt;p&gt;We'll start by adding the &lt;code&gt;@angular/pwa&lt;/code&gt; package to our app, which will automatically bootstrap the manifest and service worker config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng add @angular/pwa &lt;span class="nt"&gt;--project&lt;/span&gt; app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Where &lt;code&gt;app&lt;/code&gt; is the name of your Angular project in &lt;code&gt;angular.json&lt;/code&gt;.) This will create the &lt;code&gt;ngsw-config.json&lt;/code&gt; config file, as well as the manifest in &lt;code&gt;src/manifest.webmanifest&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;ngsw-config.json&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The Angular service worker can be configured through the &lt;code&gt;ngsw-config.json&lt;/code&gt; file. By modifying this file, we can tell the service-worker for our app to automatically pre-fetch all assets for the application. That way, when the app goes offline, it can still load the front-end resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; that the service-worker will cache other XHR headers with the proper cache headers, but if your application relies on API requests to start, you should account for that in the app's code using things like &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API" rel="noopener noreferrer"&gt;IndexedDB&lt;/a&gt; or &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage" rel="noopener noreferrer"&gt;localStorage&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./node_modules/@angular/service-worker/config/schema.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"assetGroups"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"installMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prefetch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"resources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"/favicon.ico"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"/index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"/manifest.webmanifest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"/*.css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"/*.js"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"installMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prefetch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"updateMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prefetch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"resources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"/assets/**"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's a sample config file. The &lt;code&gt;index&lt;/code&gt; key specifies the entry-point to your application. For most Angular apps, this will be &lt;code&gt;index.html&lt;/code&gt; since that's the file first loaded.&lt;/p&gt;

&lt;p&gt;Then, the front-end assets are split into two groups. The &lt;code&gt;app&lt;/code&gt; group matches any &lt;em&gt;built&lt;/em&gt; files that are necessary to boot the Angular app. The &lt;code&gt;assets&lt;/code&gt; group matches any additional assets like images, fonts, and external files.&lt;/p&gt;

&lt;p&gt;In this example, I've set both groups to &lt;code&gt;prefetch&lt;/code&gt;, which means that the service-worker will try to cache them in the background the first time the app is loaded. This ensures that they are always available offline, as long as they had time to load once. However, it can be more taxing for the first load.&lt;/p&gt;

&lt;p&gt;To avoid this, you can set an asset group to &lt;code&gt;installMode: lazy&lt;/code&gt;. This will cache the resources offline only once the front-end tries to load them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Web Manifest
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;@angular/pwa&lt;/code&gt; package will also generate a web manifest for your application in &lt;code&gt;src/manifest.webmanifest&lt;/code&gt;. Here, you can customize things like your application's name, background colors, and icons:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Noded"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"short_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Noded"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"theme_color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#3A86FF"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"background_color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#fafafa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"display"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"standalone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"start_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"icons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assets/icons/icon-72x72.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sizes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"72x72"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image/png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"purpose"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"maskable any"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assets/icons/icon-96x96.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sizes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"96x96"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image/png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"purpose"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"maskable any"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Angular will auto-generate PWA icons in the &lt;code&gt;assets/icons/&lt;/code&gt; directory, so you'll want to customize those to match your app. These icons will become the home-screen icon for you app when a user installs it.&lt;/p&gt;

&lt;center&gt;
    &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fpwa-3.png" width="333" height="394"&gt;
    &lt;br&gt;
    &lt;small&gt;Noded's PWA icon when added to my home screen.&lt;/small&gt;
&lt;/center&gt;

&lt;p&gt;A few other notes about the web manifest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;scope&lt;/code&gt; property defines the scope of pages in the web app that can be navigated to in the "app mode." If your app tries to load a route that's outside of the scope, the client will revert to a web-browser rather than immersive mode.

&lt;ul&gt;
&lt;li&gt;This property is relative to the entry point of the application. So, if the entry point is &lt;code&gt;/index.html&lt;/code&gt;, then the scope &lt;code&gt;./*&lt;/code&gt; matches all routes &lt;code&gt;/**&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;The &lt;code&gt;start_url&lt;/code&gt; is the route that is loaded when the user launches the PWA. Usually, this should match the entry point in the &lt;code&gt;ngsw-config.json&lt;/code&gt; file as &lt;code&gt;index.html&lt;/code&gt;.&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Building your application
&lt;/h3&gt;

&lt;p&gt;Now that we've set up the Angular service-worker, you should be able to build your app and have it appear as a PWA in the browser. You can do this as you normally would. Since &lt;a href="https://code.garrettmills.dev/Noded/frontend/src/branch/master/.drone.yml#L21" rel="noopener noreferrer"&gt;Noded&lt;/a&gt; is an Ionic app, I'll use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./node_modules/.bin/ionic build &lt;span class="nt"&gt;--prod&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the &lt;code&gt;ngsw-config.json&lt;/code&gt;, this will generate a few new files. If you look at &lt;code&gt;www/ngsw.json&lt;/code&gt;, you can see the compiled config for the service-worker telling it the locations of all generated files for your app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"configVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1606842506052&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"assetGroups"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"installMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prefetch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"updateMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prefetch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cacheQueryOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ignoreVary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"urls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"/10-es2015.8900b72b6fdc6cff9bda.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"/10-es5.8900b72b6fdc6cff9bda.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"/11-es2015.82443d43d1a7c061f365.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"/11-es5.82443d43d1a7c061f365.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"/12-es2015.617954d1af39ce4dad1f.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"/12-es5.617954d1af39ce4dad1f.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"/13-es2015.eb9fce554868e6bda6be.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how the service-worker knows what to fetch and cache when running your application. It also writes the &lt;code&gt;ngsw-worker.js&lt;/code&gt; file, which is the actual service worker code that gets run by the browser in the background. The web manifest is also included in the build.&lt;/p&gt;

&lt;p&gt;Once you deploy your app and load it in the browser, it should now appear to have both a web manifest and a service worker:&lt;/p&gt;

&lt;center&gt;
    &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fpwa-4.png" width="608" height="702"&gt;
    &lt;br&gt;
    &lt;small&gt;You can view this on the "Application" tab of your browser's dev-tools.&lt;/small&gt;
    &lt;br&gt;&lt;br&gt;
    &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fpwa-5.png" width="619" height="285"&gt;
&lt;/center&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; that the service worker will only register and run if it is configured properly &lt;em&gt;and&lt;/em&gt; your application is served over HTTPS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running in a sub-route (&lt;code&gt;/app&lt;/code&gt;, &amp;amp;c.)
&lt;/h3&gt;

&lt;p&gt;You may have noticed in the screen-shot above that the service-worker for Noded is registered for &lt;code&gt;noded.garrettmills.dev/i&lt;/code&gt;. This is because the Angular app for Noded runs in the &lt;code&gt;/i&lt;/code&gt; sub-route of the domain. This requires special consideration for the service-worker.&lt;/p&gt;

&lt;p&gt;Recall that the manifest has a &lt;code&gt;scope&lt;/code&gt; and &lt;code&gt;start_url&lt;/code&gt;, and the &lt;code&gt;ngsw.json&lt;/code&gt; has an &lt;code&gt;index&lt;/code&gt; key. These are relative to the root of the domain, not the application. So, in order to serve our Angular app from a sub-route, we need to modify the PWA configs. Luckily, the Angular service-worker has a CLI tool that makes this easy for us. After we build our application, we can use the &lt;code&gt;ngsw-config&lt;/code&gt; command to re-generate the config to use a sub-route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./node_modules/.bin/ngsw-config ./www/ ./ngsw-config.json /i
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last argument is the sub-route where your application lives. In my case, that's &lt;code&gt;/i&lt;/code&gt;. This command will modify the service-worker config to use the sub-route for all resources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"configVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1606843244002&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/i/index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"assetGroups"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"installMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prefetch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"updateMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prefetch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cacheQueryOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ignoreVary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"urls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"/i/10-es2015.8900b72b6fdc6cff9bda.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"/i/10-es5.8900b72b6fdc6cff9bda.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"/i/11-es2015.82443d43d1a7c061f365.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"/i/11-es5.82443d43d1a7c061f365.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"/i/12-es2015.617954d1af39ce4dad1f.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"/i/12-es5.617954d1af39ce4dad1f.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that your service worker caches the correct files. (&lt;strong&gt;Note&lt;/strong&gt; that this doesn't actually need to modify the web manifest.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging
&lt;/h2&gt;

&lt;p&gt;Once you've deployed your built app, it should start caching assets through the service-worker. However, if this doesn't happen, here are a few things to consider.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't modify the compiled Angular code
&lt;/h3&gt;

&lt;p&gt;Once your app has been compiled to the &lt;code&gt;www/&lt;/code&gt; directory, &lt;em&gt;never&lt;/em&gt; modify these files. If you need to make changes, use substitutions in the &lt;code&gt;angular.json&lt;/code&gt;, or just change the original source files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"hashTable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"/i/10-es2015.8900b72b6fdc6cff9bda.js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"d3cf604bab1f99df8bcf86d7a142a3a047c66dd2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"/i/10-es5.8900b72b6fdc6cff9bda.js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8fcf65ea8740ae0364cd7371dd478e05eadb8b35"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"/i/11-es2015.82443d43d1a7c061f365.js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bc50afb2730b9662fc37a51ae665fd30a9b0637c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"/i/11-es5.82443d43d1a7c061f365.js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"300d5e62ec8ed5a744ac0dc1c2d627d6208499d7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"/i/12-es2015.617954d1af39ce4dad1f.js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"465dd6ae6336dee028f3c2127358eea1d914879d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"/i/12-es5.617954d1af39ce4dad1f.js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5549d758aea47ab6d81a45d932993a6da9f5289c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"/i/13-es2015.eb9fce554868e6bda6be.js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2ca9cc161ae45c0a978b8bebce3f6dd7597bba07"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"/i/13-es5.eb9fce554868e6bda6be.js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1dadc7f0083a1d499ea80f9c56d9ad62de96c4f3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason for this is because the Angular service-worker generates hashes of the generated files and checks them on download. This is how it knows whether it has cached the latest version of the file or not. If you manually modify the compiled file, the hash won't match, and the service-worker will invalidate its entire cache.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bypass the service-worker
&lt;/h3&gt;

&lt;p&gt;As mentioned above, the service-worker will attempt to cache other outbound requests, provided that the server responds with appropriate cache headers. However, there may be instances where you want to prevent this behavior (for example, when checking if the app is online and can access the server). To do this, you can add the &lt;code&gt;?ngsw-bypass&lt;/code&gt; query parameter to the URLs of your requests.&lt;/p&gt;

&lt;p&gt;Example: &lt;code&gt;/api/v1/stat?ngsw-bypass&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  View service-worker logs
&lt;/h3&gt;

&lt;p&gt;If you are having issues with the service worker's cache, it can be difficult to narrow them down without logs. You can view debugging output from the Angular service-worker by navigating to the &lt;code&gt;/ngsw/state&lt;/code&gt; route in your app. In my case, that's &lt;code&gt;https://noded.garrettmills.dev/i/ngsw/state&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NGSW Debug Info:

Driver state: NORMAL ((nominal))
Latest manifest hash: none
Last update check: never



=== Idle Task Queue ===
Last update tick: never
Last update run: never
Task queue:


Debug log:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are having issues, the &lt;code&gt;Debug log&lt;/code&gt; section can provide more info on cache invalidation and other issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  View cached files
&lt;/h3&gt;

&lt;p&gt;You can view the status of cached files in the "Storage" section of your browser's dev tools. This can help you see if the service worker was unable to find files (invalid route configurations), or was invalidating cached files.&lt;/p&gt;

&lt;center&gt;
    &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.garrettmills.dev%2Fassets%2Fblog-images%2Fpwa-6.png" width="624" height="676"&gt;
    &lt;br&gt;&lt;small&gt;Files cached locally by Noded's service worker.&lt;/small&gt;
&lt;/center&gt;

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

&lt;p&gt;This was a cursory look at getting your Angular/Ionic app set up as a PWA and caching assets offline using Angular service-workers. If your app relies on back-end resources (like an API), you'll still need to account for that when adding offline support using tools like IndexedDB and localStorage.&lt;/p&gt;

&lt;p&gt;For example, Noded has an &lt;a href="https://code.garrettmills.dev/Noded/frontend/src/branch/master/src/app/service/api.service.ts" rel="noopener noreferrer"&gt;API service&lt;/a&gt; that sits between the app and the server and caches API resources locally in the IndexedDB. Perhaps we'll look into this more in a future post.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>angular</category>
      <category>pwa</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
