<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Lucas Simon's Blog]]></title><description><![CDATA[Lucas Simon's Blog]]></description><link>https://blog.lucas-simon.com</link><generator>RSS for Node</generator><lastBuildDate>Sun, 17 May 2026 05:03:34 GMT</lastBuildDate><atom:link href="https://blog.lucas-simon.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Avoiding circular dependencies]]></title><description><![CDATA[A circular dependency is when two pieces of code (files, classes, functions, etc) need each other to work (they're typically bad practice since they make code too intertwined). We rarely intentionally add circular dependencies, but they often arise a...]]></description><link>https://blog.lucas-simon.com/avoiding-circular-dependencies</link><guid isPermaLink="true">https://blog.lucas-simon.com/avoiding-circular-dependencies</guid><category><![CDATA[dependency injection]]></category><category><![CDATA[circular dependency]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[clean code]]></category><dc:creator><![CDATA[Lucas Simon]]></dc:creator><pubDate>Tue, 26 Sep 2023 06:00:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/YuQEEaNOgBA/upload/d0d5cc3a7a847c8842fe8675997430f8.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A circular dependency is when two pieces of code (files, classes, functions, etc) need each other to work (they're typically bad practice since they make code too intertwined). We rarely intentionally add circular dependencies, but they often arise as logic and business requirements become more complicated. They usually occur because two requirements depend on each other (e.g. When sending a <strong>team</strong> invite to an email, check if a <strong>user</strong> with that email exists. When deleting a <strong>user</strong>, remove them from all <strong>teams</strong>).</p>
<h3 id="heading-solution-1-moving-code-to-a-new-servicemodule">Solution 1: Moving Code to a New Service/Module</h3>
<p>Moving code is typically the easiest solution. Say we have the following requirements:</p>
<ul>
<li><p>Allow members of a task to add comments to incomplete tasks</p>
</li>
<li><p>Members can invite other users to incomplete tasks</p>
</li>
<li><p>A task is complete once every member has approved of it</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> TaskService {
  <span class="hljs-comment">// * A task is complete once every member has approved of it</span>
  isTaskComplete(id) {
    <span class="hljs-keyword">const</span> members = <span class="hljs-built_in">this</span>.taskMembersService.getAll(id);
    <span class="hljs-keyword">return</span> members.every(<span class="hljs-function"><span class="hljs-params">member</span> =&gt;</span> member.hasApprovedTask);
  }

  <span class="hljs-comment">// * Allow members of a task to add comments to incomplete tasks</span>
  addComment(user, id, comment) {
    <span class="hljs-built_in">this</span>.taskMembersService.assertIsMember(user);
    <span class="hljs-comment">// Don't allow more comments once complete</span>
    assert(!<span class="hljs-built_in">this</span>.isTaskComplete(id));
    <span class="hljs-comment">// ... Add comment ...</span>
  }
}

<span class="hljs-keyword">class</span> TaskMembersService {
  assertIsMember(user) {}
  getAll(taskId) {}

  <span class="hljs-comment">// * Members can invite other users to incomplete tasks</span>
  inviteMember(taskId, fromUser, inviteUserId) {
    <span class="hljs-comment">// Don't allow inviting to a complete task</span>
    assert(!<span class="hljs-built_in">this</span>.taskService.isTaskComplete(taskId));
    <span class="hljs-built_in">this</span>.assertIsMember(fromUser);
    <span class="hljs-comment">// ... Add inviteUserId member ...</span>
  }
}
</code></pre>
<p><code>TaskService &lt;--&gt; TaskMembersService</code></p>
<p>The logic is completely reasonable, and it makes sense how this structure would naturally evolve (e.g. comments existed before we started having task membership). But, of course, we have issues:</p>
<ul>
<li><p>Before adding a comment (TaskService), check if they're a member of the task (TaskMembersService)</p>
</li>
<li><p>When inviting a member (TaskMembersService), check that the task is still open (TaskService)</p>
</li>
<li><p>When checking if a task is open (TaskService), make sure all members (TaskMembersService) have approved of it</p>
</li>
</ul>
<hr />
<p>The problem is that we've made the TaskService a bit too generic. Tasks should be able to exist without members or comments; however, the two methods in our TaskService depend on those.</p>
<p>Without members, <code>isTaskComplete()</code> is nonsense, because a task is complete when all members approve of it. Additionally, <code>addComment()</code> could be described as "Allows a member to add a comment to an open task". This means <code>addComment()</code> correctly depends on <code>isTaskComplete()</code>, especially because we would <em>want</em> <code>addComment()</code> to error if we ever removed the <code>isTaskComplete()</code> function (because to meet the above description for the function, we need a way to check if a task is complete).</p>
<p>So the new service dependencies could look like:</p>
<p><code>TaskCommentService -&gt; TaskStatusService -&gt; TaskMembersService -&gt; TaskService</code></p>
<p>We can do a sanity check by ensuring that, if we remove any service, we expect the dependency to produce an error (e.g. if we remove the <code>MembersService</code>, <code>StatusService</code> should break because <code>isTaskComplete()</code> requires the existence of members). This is under the assumption that the business requirements for all other services remain unchanged.</p>
<h3 id="heading-solution-2-adding-eventshooks">Solution 2: Adding events/hooks</h3>
<p>If your requirement can be described as "When X, do Y", you can likely use events or hooks. For example, when a user deletes their account, remove any teams they're on:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> TeamService {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> userService: UserService,
  </span>) {
    <span class="hljs-comment">// An event is called and forgotten about</span>
    <span class="hljs-built_in">this</span>.userService.on(<span class="hljs-string">'delete-account'</span>, <span class="hljs-built_in">this</span>.onDeleteAccount.bind(<span class="hljs-built_in">this</span>));
    <span class="hljs-comment">// onDeleteAccount(user) {}</span>

    <span class="hljs-comment">// In contrast, a hook will wait until the callback is completed</span>
    <span class="hljs-comment">// before deleting the account. This ensures we're never</span>
    <span class="hljs-comment">// left in a position where we're trying to delete an</span>
    <span class="hljs-comment">// account but fail to remove them from teams.</span>
    <span class="hljs-built_in">this</span>.userService.deleteAccountHooks.add(<span class="hljs-built_in">this</span>.onDeleteAccount.bind(<span class="hljs-built_in">this</span>));
    <span class="hljs-comment">// onDeleteAccount(user, sqlTransaction) {}</span>
  }
}
</code></pre>
<p>The UserService does not need to know that teams exist at all. If you don't totally know what needs to depend on what, consider the direction of data flow. If I delete the <code>team</code> and <code>team_members</code> table, does the <code>user</code> table still work? Yes? Then the UserService should not know that teams exist. This gives us more flexibility:</p>
<ul>
<li><p>If we want to delete teams and the <code>TeamService</code> for any reason, there's less depending on it. "Remove a user from teams when their account is deleted" is inherently team logic and should be removed when deleting the <code>TeamService</code> (because that logic doesn't make sense without teams).</p>
</li>
<li><p>If we want to expand <code>TeamService</code> for any reason, there's less depending on it. For example, if we want team members that can either be a user <em>or</em> an AI (why not), we don't have to worry about a single line of code in the <code>UserService</code> going "oh no, how do I handle the case of an AI team member?"</p>
</li>
</ul>
<h3 id="heading-what-about-a-more-direct-circular-dependency">What about a more direct circular dependency?</h3>
<p>Consider the requirements:</p>
<ul>
<li><p>When the CreateTournament job is triggered, we should create a tournament</p>
</li>
<li><p>After a tournament is created, schedule any child tournaments (if applicable)</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> CreateTournamentJob {
  add(tournamentOpt, atTime) { ... }

  <span class="hljs-comment">// This is called when it is time for the job to run</span>
  process(tournamentOpt) {
    <span class="hljs-built_in">this</span>.verifierService.assertValidOpt(tournamentOpt);
    <span class="hljs-built_in">this</span>.tournamentService.create(tournamentOpt);
  }
}

<span class="hljs-keyword">class</span> TournamentService {
  create(tournamentOpt) {
    <span class="hljs-comment">// ... Create the tournament ...</span>

    <span class="hljs-comment">// Schedule child tournaments to be created 5 minutes from now</span>
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> childOpt <span class="hljs-keyword">of</span> tournamentOpt.childrenOpt) {
      <span class="hljs-built_in">this</span>.createTournamentJob.add(childOpt, <span class="hljs-built_in">Date</span>.now() + FIVE_MINUTES);
    }
  }
}
</code></pre>
<p>There are a couple of ways we can handle this, and it all depends on what our requirements are:</p>
<h4 id="heading-hook-on-create-tournament">Hook on "create tournament"</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> CreateTournamentJob {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> tournamentService: TournamentService</span>) {
    <span class="hljs-built_in">this</span>.tournamentService.onCreateTournamentsHooks.add(<span class="hljs-keyword">async</span> (opt) =&gt; {
      <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> childOpt <span class="hljs-keyword">of</span> opt.childrenOpt) {
        <span class="hljs-built_in">this</span>.add(childOpt, <span class="hljs-built_in">Date</span>.now() + FIVE_MINUTES);
      }
    });
  }

  add(tournamentOpt, atTime) {}

  process(tournamentOpt) {
    <span class="hljs-built_in">this</span>.verifierService.assertValidOpt(tournamentOpt);
    <span class="hljs-built_in">this</span>.tournamentService.create(tournamentOpt);
  }
}
</code></pre>
<p>This only makes sense if deleting the tournament job should make it so we no longer schedule child tournaments. <strong>If this isn't your requirement, you will run into issues.</strong> For example, perhaps you manually added tournaments to the tournament job in the past and no longer need that. You delete <code>CreateTournamentJob</code> and poof, suddenly child tournaments aren't generating anymore... but there weren't any errors!</p>
<h4 id="heading-return-from-create">Return from create()</h4>
<p>Perhaps in another world, it makes sense to return the child tournaments so they can be acted upon. This would make sense only if we wanted to schedule child tournaments when calling from the scheduler, but handle them differently elsewhere:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> CreateTournamentJob {
  add(tournamentOpt, atTime) {}

  process(tournamentOpt) {
    <span class="hljs-keyword">const</span> childTourns = <span class="hljs-built_in">this</span>.tournamentService.create(tournamentOpt);
    <span class="hljs-comment">// ... Schedule child tournaments ...</span>
  }
}

<span class="hljs-keyword">class</span> TournamentService {
  create(tournamentOpt) {
    <span class="hljs-comment">// ... Create the tournament ...</span>
    <span class="hljs-keyword">return</span> tournamentOpt.childrenOpt.map(<span class="hljs-function"><span class="hljs-params">what</span> =&gt;</span> ever)
  }

  <span class="hljs-comment">// Maybe called from an admin panel, or if users are allowed to make their own tournaments</span>
  manuallyCreate(byUser, tournamentOpt) {
    <span class="hljs-keyword">const</span> childTourns = <span class="hljs-built_in">this</span>.create(tournamentOpt);
    <span class="hljs-comment">// ... Handle child tournaments, e.g. maybe we create them immediately in this case</span>
  }
}
</code></pre>
<h4 id="heading-or-leave-the-circular-dependency">Or, leave the circular dependency</h4>
<p>Or, the final case is that the circular dependency <em>makes</em> sense. If we remove "creating tournaments", we WANT the job to fail. If we remove the job, we WANT "creating tournaments" to fail because otherwise it can't handle child tournaments correctly.</p>
<h3 id="heading-wrapping-up">Wrapping Up</h3>
<p>We covered 3 ways to resolve circular dependencies:</p>
<ol>
<li><p>Splitting code up into different files</p>
</li>
<li><p>Using hook/events</p>
</li>
<li><p>Returning data so the caller</p>
</li>
</ol>
<p>But ultimately, in some cases it may even make sense to leave a circular dependency in place if it aligns with your business requirements. Updating and deleting code are preferable over new code, but with messy depdenencies, they're likely to be more pain than they're worth. Make it easy to update and delete and you'll find yourself with a smaller codebase doing the exact same thing.</p>
<p>The easiest way to check if you have your dependencies set up well is to ask yourself, for any given piece of code: If I delete this code/edit the types &amp; functionality, are all the errors representative of all the areas that need their business requirements changed? Or is it merely busy work to fix the error? By doing so, you can avoid potential pitfalls and create software that is easier to understand, maintain, and expand. Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[The 9 Services You Need for a Scalable Startup]]></title><description><![CDATA[Manually cloning your repo and running your app on a random digital ocean VPS is not sustainable or scalable. I built a startup as a solo dev that scaled to over 1,000,000 users (with up to 10,000+ concurrent connections) and these are the services t...]]></description><link>https://blog.lucas-simon.com/the-services-you-need-for-a-scalable-startup</link><guid isPermaLink="true">https://blog.lucas-simon.com/the-services-you-need-for-a-scalable-startup</guid><category><![CDATA[Startups]]></category><category><![CDATA[services]]></category><category><![CDATA[scalability]]></category><category><![CDATA[Databases]]></category><category><![CDATA[tools]]></category><dc:creator><![CDATA[Lucas Simon]]></dc:creator><pubDate>Mon, 09 Jan 2023 17:32:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/c9c4b3d3bc537a44571e45aa7291b979.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Manually cloning your repo and running your app on a random digital ocean VPS is not sustainable or scalable. I built a startup as a solo dev that scaled to over 1,000,000 users (with up to 10,000+ concurrent connections) and these are the services that I used to get there (and what it cost me).</p>
<h2 id="heading-kubernetes-google-cloud-or-digital-ocean">Kubernetes (Google Cloud or Digital Ocean)</h2>
<p>When I started my project, I used Kubernetes. Many claim it's over-complicated, and, if you don't plan to scale, it probably is. But if you want to build an app that will grow, it will pay off immensely. Not only is it trivial to scale (you can easily scale containers by CPU load with HPAs), it makes setting up new services less of a hassle. For example, in my last project, there was a Python library and a C# library that didn't have replacements in TypeScript, so I simply built wrappers around them and turned them into services. If setting up a service wasn't as trivial, I might've tried to make do with adding to the monolith, even if creating a new service was the right thing to do.</p>
<p>I used <a target="_blank" href="https://cloud.google.com/kubernetes-engine">Google Cloud</a> at the time. It's fine, but I do want to try <a target="_blank" href="https://www.digitalocean.com/products/kubernetes">Digital Ocean</a> next, since GCloud had a ton of features that I rarely, making their interface more complicated.</p>
<p>Additionally, I was not using Terraform at the time, but I probably would now (since manually applying all the Kubernetes files was error-prone).</p>
<p>P.S. You'll need a container registry so Kubernetes has somewhere to pull your Docker images from. Both GCloud and Digital Ocean support this.</p>
<h2 id="heading-postgres-and-redis">Postgres and Redis</h2>
<p>Pick the right tool, of course—if you know NoSQL or another database is right for your project, use that. However, even in the constantly-changing database schemas of the startup world, Postgres (with an ORM that can handle migrations smoothly) is rarely a bad choice.</p>
<p>Redis (or an alternative) is a must-have. You can reduce your Postgres queries five or tenfold with proper caching. Without Redis, your primary database will likely be your largest cost. In addition, it's a must for scaling; I've never had a website go down under Redis load, but I've had many issues with Postgres getting overloaded (unoptimized queries, spike in traffic that maxes out its connections, etc).</p>
<h2 id="heading-new-relicdatadog-and-pagerduty">New Relic/Datadog and Pagerduty</h2>
<p>At some point, something will go wrong. If you have 200 concurrent users on your site for a growing startup, going down for an hour sucks. Going down overnight is terrible. Pager Duty is free for solo devs, so there's not much of an excuse not to use it.</p>
<p><a target="_blank" href="https://www.datadoghq.com/">Datadog</a> and <a target="_blank" href="https://newrelic.com/">New Relic</a> are up to personal preference—I did some research back in 2019 and picked Datadog. I did some research recently and would instead choose New Relic due to its friendly pricing for solo devs. Do your research, but as of right now, it seems to be mainly pricing differences for <em>roughly</em> the same set of features (logging, APM tracing, metrics, etc).</p>
<h2 id="heading-sentry">Sentry</h2>
<p><a target="_blank" href="https://sentry.io/welcome/">Sentry</a> for frontend errors. Not much to say here; I'm sure there are alternatives, but Sentry works great and has a free plan to get started. Make sure to consistently update your frontend to block unnecessary errors so you don't eat into your quota with false positives.</p>
<h2 id="heading-redash-visualize-sql-queries">Redash (Visualize SQL Queries)</h2>
<p>You don't need to pick <a target="_blank" href="https://redash.io/">Redash</a> specifically, but anything that will help you visualize queries. It's a pain to make business decisions by writing queries, importing them to Google Sheets or Excel, and then creating graphs.</p>
<h2 id="heading-github-or-gitlab">Github or Gitlab</h2>
<p>I chose <a target="_blank" href="https://about.gitlab.com/">Gitlab</a> for my last project, simply because it was free for private repos (and I liked the ability to self-host in the future). If you're a solo dev, it doesn't <em>really</em> matter—and you can always transfer from one to the other later. For what it's worth, <a target="_blank" href="https://github.com/">Github</a> has more free CI/CD minutes than Gitlab.</p>
<h2 id="heading-estimated-cost">Estimated Cost</h2>
<p><em>Redis, Postgres, Kubernetes, and Container Registry pricing are all based on Digital Ocean.</em></p>
<ol>
<li><p><strong>Kubernetes - $48/mo to start</strong> (represents two nodes each with 2 vCPUs and 2.5GB of RAM; you may need more or less, this one is hard to predict)</p>
</li>
<li><p><strong>Container Registry - $5/mo</strong></p>
</li>
<li><p><strong>Redis - $15/mo</strong></p>
</li>
<li><p><strong>Postgres - $15/mo</strong> (at least, may become $30/mo or $60/mo in the first couple of months depending on your needs)</p>
</li>
<li><p><strong>Pager Duty - Free for solo devs!</strong></p>
</li>
<li><p><strong>New Relic - Free-ish</strong> (They charge a reasonable rate for data and a hefty cost per user after the first, so great for solo devs, but pricey as your team scales)</p>
</li>
<li><p><strong>Sentry</strong> - Free (You'll likely need to upgrade to $26/mo in the first couple of months)</p>
</li>
<li><p><strong>Redash</strong> - $12/mo (Or $0 if you host locally)</p>
</li>
<li><p><strong>GitHub - Free for solo devs!</strong></p>
</li>
</ol>
<p><strong>Total: $95/mo</strong> (could be cheaper if you choose lower-cost machines and self-host Redash)</p>
<h2 id="heading-reduce-costs">Reduce Costs</h2>
<p>In your first few months, you're probably not making any money, but your costs will go up. Here are some ways to keep them manageable (this could probably be its own article, but whatever, bonus content):</p>
<ul>
<li><p><strong>Postgres Storage</strong>: Look for cheaper options for write-only data or data that doesn't need to be accessed often (e.g. metrics in longer-term storage, calculated data that could instead be cached, etc)</p>
</li>
<li><p><strong>Postgres CPU/Memory</strong>: Cache more</p>
</li>
<li><p><strong>Kubernetes Node CPU/Memory</strong>: Optimize your app (duh). Also cache more! E.g. it may be lower cost to host your frontend statically VS running an nginx server in a pod.</p>
</li>
<li><p><strong>Sentry/New Relic/Datadog</strong>: Watch what data you're sending—gobs of unnecessary logs or errors can sometimes be forwarded. Setup some kind of ingestion control so you're only intaking 50% or 25% of APM traces.</p>
</li>
<li><p><strong>Container Registry</strong>: Delete outdated images.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Amazon CodeWhisperer vs Github Copilot (Updated April 2023)]]></title><description><![CDATA[Context
Github Copilot is an ML-powered autocompletion tool for developers (it uses OpenAI's Codex). It is currently has a 1-month free trial and is priced at $10/mo.
Amazon CodeWhisperer is a similar service. It's currently in private preview, so it...]]></description><link>https://blog.lucas-simon.com/amazon-codewhisperer-vs-github-copilot</link><guid isPermaLink="true">https://blog.lucas-simon.com/amazon-codewhisperer-vs-github-copilot</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[github copilot]]></category><category><![CDATA[#CodeWhisperer]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Lucas Simon]]></dc:creator><pubDate>Thu, 14 Jul 2022 19:55:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1657763604170/KtQZZ0jNT.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-context">Context</h1>
<p><a target="_blank" href="https://github.com/features/copilot">Github Copilot</a> is an ML-powered autocompletion tool for developers (it uses OpenAI's Codex). It is currently has a 1-month free trial and is priced at $10/mo.</p>
<p><a target="_blank" href="https://aws.amazon.com/codewhisperer/">Amazon CodeWhisperer</a> is a similar service. It's currently in private preview, so its quality and feature set may change before release.</p>
<h1 id="heading-comparison">Comparison</h1>
<p>Each of these tests is conducted from a blank page in VSCode with a language selected (typically Javascript unless otherwise stated). I will include the prompt and the results that both Copilot and CodeWhisperer suggested, along with any interesting items to notice.</p>
<h4 id="heading-basic-variables-similar">Basic Variables (Similar)</h4>
<pre><code class="lang-js"><span class="hljs-comment">// Prompt</span>
<span class="hljs-keyword">const</span> a = <span class="hljs-number">1</span>;
<span class="hljs-keyword">const</span> b = <span class="hljs-number">2</span>;
<span class="hljs-keyword">const</span> sum =
</code></pre>
<pre><code class="lang-js"><span class="hljs-comment">// Copilot</span>
<span class="hljs-keyword">const</span> sum = a + b;

<span class="hljs-comment">// CodeWhisperer (July 2022)</span>
<span class="hljs-keyword">const</span> sum = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> a + b;
}

<span class="hljs-comment">// CodeWhisperer (April 2023)</span>
<span class="hljs-keyword">const</span> sum = a + b;
</code></pre>
<h4 id="heading-trigonometry-same">Trigonometry (Same)</h4>
<pre><code class="lang-js"><span class="hljs-comment">// Prompt</span>
<span class="hljs-keyword">const</span> x = <span class="hljs-built_in">Math</span>.cos(angle) * radius;
</code></pre>
<pre><code class="lang-js"><span class="hljs-comment">// All</span>
<span class="hljs-keyword">const</span> y = <span class="hljs-built_in">Math</span>.sin(angle) * radius;
</code></pre>
<h4 id="heading-factorial-near-identical">Factorial (Near Identical)</h4>
<pre><code class="lang-js"><span class="hljs-comment">// Prompt</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">factorial</span></span>
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-comment">// Copilot</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">factorial</span>(<span class="hljs-params">n</span>) </span>{
  <span class="hljs-keyword">if</span> (n === <span class="hljs-number">0</span>) {
    <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;
  }
  <span class="hljs-keyword">return</span> n * factorial(n - <span class="hljs-number">1</span>);
}

<span class="hljs-comment">// CodeWhisperer (July 2022 and April 2023)</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">factorial</span>(<span class="hljs-params">n</span>) </span>{
  <span class="hljs-keyword">if</span> (n === <span class="hljs-number">1</span>) {
    <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;
  }
  <span class="hljs-keyword">return</span> n * factorial(n - <span class="hljs-number">1</span>);
}
</code></pre>
<h4 id="heading-price-of-btc">Price of BTC</h4>
<pre><code class="lang-plaintext">// Get price of BTC
function
</code></pre>
<pre><code class="lang-js"><span class="hljs-comment">// Copilot (July 2022)</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPrice</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    request(<span class="hljs-string">'https://api.coinmarketcap.com/v1/ticker/bitcoin/'</span>, <span class="hljs-function">(<span class="hljs-params">error, response, body</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (error) {
        reject(error);
      }
      resolve(body);
    });
  });
}

<span class="hljs-comment">// CodeWhisperer (July 2022)</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPrice</span>(<span class="hljs-params">callback</span>) </span>{
  $.ajax({
    <span class="hljs-attr">url</span>: <span class="hljs-string">"https://api.coindesk.com/v1/bpi/currentprice.json"</span>,
    <span class="hljs-attr">dataType</span>: <span class="hljs-string">"json"</span>,
    <span class="hljs-attr">success</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">data</span>) </span>{
      callback(data);
    }
  });
}

<span class="hljs-comment">// Copilot (April 2023)</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPrice</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">var</span> url = <span class="hljs-string">"https://api.coinbase.com/v2/prices/BTC-USD/buy"</span>;
    <span class="hljs-keyword">var</span> req = <span class="hljs-keyword">new</span> XMLHttpRequest();
    req.open(<span class="hljs-string">"GET"</span>, url, <span class="hljs-literal">false</span>);
    req.send(<span class="hljs-literal">null</span>);
    <span class="hljs-keyword">var</span> data = <span class="hljs-built_in">JSON</span>.parse(req.responseText);
    <span class="hljs-keyword">return</span> data.data.amount;
}

<span class="hljs-comment">// CodeWhisperer (April 2023)</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getBTCPrice</span>(<span class="hljs-params"></span>) </span>{
  $.getJSON(<span class="hljs-string">'https://api.coindesk.com/v1/bpi/currentprice.json'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">data</span>) </span>{
    <span class="hljs-keyword">var</span> price = data.bpi.USD.rate;
    $(<span class="hljs-string">'#btc-price'</span>).text(price);
  });
}
</code></pre>
<p>Copilot July 2022's 9 suggestions use <code>request</code>, <code>$.getJSON</code>, and even <code>new XMLHttpRequest();</code> in the last one. 5 make use of promises. Its recommendations include 3 different APIs (<code>coinmarketcap</code>, <code>coindesk</code>, and <code>coinbase</code>).</p>
<p>Copilot April 2023's first suggestion is clearly an initial different approach. It now uses <code>UrlFetchApp.fetch</code> (seems to be a Google thing), <code>new XMLHttpRequest</code>, and <code>new Request</code>. It recommends 4 different APIs (<code>coinbase</code>, <code>coindesk</code>, <code>coinmarketcap</code>, and <code>bitstamp</code>).</p>
<p>July 2022 CodeWhisperer's 5 suggestions use <code>$.ajax</code> in 4 of them (no promises) and <code>fetch</code> in 1 (with promises). They all use the <code>coindesk</code> API.  </p>
<p>April 2023 CodeWhisisper's 5 suggestion use <code>$ajax</code> in 4 of them and <code>$.getJSON</code> is 1 (no promises at all). They all use the <code>coindesk</code> API. Interestingly, they all now try to extract/display the data in some simple way (as you can see above).</p>
<h4 id="heading-price-of-btc-with-async-similar">Price of BTC with async (Similar)</h4>
<p>The above example discourages the use of promises by not starting the function with <code>async</code>.</p>
<pre><code class="lang-plaintext">// Get price of BTC
async function
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-comment">// Copilot (July 2022 and April 2023)</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPrice</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://api.coindesk.com/v1/bpi/currentprice.json'</span>);
  <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json();
  <span class="hljs-keyword">return</span> data.bpi.USD.rate_float;
}

<span class="hljs-comment">// CodeWhisperer (July 2022)</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPrice</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://api.coindesk.com/v1/bpi/currentprice.json'</span>);
  <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json();
  <span class="hljs-keyword">return</span> data;
}

<span class="hljs-comment">// CodeWhisperer (April 2023)</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPrice</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://api.coindesk.com/v1/bpi/currentprice.json'</span>);
    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json();
    <span class="hljs-keyword">return</span> data.bpi.USD.rate_float;
}
</code></pre>
<p>It's impressive how similar these are. I appreciate that Copilot actual gets the raw price.</p>
<p>CodeWhisperer April 2023 comes around and becomes identical to Copilot.</p>
<h4 id="heading-send-email-near-identical">Send Email (Near Identical)</h4>
<p>I've specifically added <code>from,</code> to the prompt because Copilot stops its autocomplete as soon as it comes across an email (potentially causing syntax errors). This is not equivalent to OpenAI Codex's behavior, which has no issue completing the <code>from</code> field (with an email such as <code>no-reply@todo.com</code>).</p>
<p>We'll use SendGrid specifically. When not specifying sendgrid, Copilot is a little more diverse in its recommendations, whereas CodeWhisperer will only use <code>aws-sdk</code>.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> sgMail <span class="hljs-keyword">from</span> <span class="hljs-string">'@sendgrid/mail'</span>;
<span class="hljs-keyword">const</span> SEND_GRID_API_KEY = process.env.SEND_GRID_API_KEY;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendEmail</span>(<span class="hljs-params">from,</span></span>
</code></pre>
<pre><code class="lang-js"><span class="hljs-comment">// Copilot July 2022, Copilot April 2023, CodeWhisperer April 2023</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendEmail</span>(<span class="hljs-params">from, to, subject, text</span>) </span>{
  sgMail.setApiKey(SEND_GRID_API_KEY);
  <span class="hljs-keyword">const</span> msg = {
    to,
    <span class="hljs-keyword">from</span>,
    subject,
    text,
  };
  sgMail.send(msg);
}

<span class="hljs-comment">// CodeWhisperer (July 2022)</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendEmail</span>(<span class="hljs-params">from, to, subject, html</span>) </span>{
  sgMail.setApiKey(SEND_GRID_API_KEY);
  <span class="hljs-keyword">const</span> msg = {
    to,
    <span class="hljs-keyword">from</span>,
    subject,
    html,
  };
  sgMail.send(msg);
}
</code></pre>
<p>Only the old version of CodeWhisperer is a bit different.</p>
<h4 id="heading-naming-consistency-same">Naming Consistency (Same)</h4>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> people = [
  { <span class="hljs-attr">theirNameIs</span>: <span class="hljs-string">'John'</span>, <span class="hljs-attr">andTheirAgeShallBe</span>: <span class="hljs-number">20</span> },
</code></pre>
<pre><code class="lang-js"><span class="hljs-comment">// All</span>
  { <span class="hljs-attr">theirNameIs</span>: <span class="hljs-string">'Jane'</span>, <span class="hljs-attr">andTheirAgeShallBe</span>: <span class="hljs-number">21</span> },
</code></pre>
<h4 id="heading-halting-problem">Halting Problem</h4>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">run</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">let</span> a = <span class="hljs-number">10</span>;
  <span class="hljs-keyword">while</span> (a &gt; <span class="hljs-number">0</span>) {
    a++;
  }
  <span class="hljs-keyword">return</span> a;
}
<span class="hljs-comment">// Explain in a couple sentences if program above halts or not, and why.</span>
<span class="hljs-comment">// The program will</span>
</code></pre>
<pre><code class="lang-js"><span class="hljs-comment">// Copilot (July 2022)</span>
<span class="hljs-comment">// The program will halt because a is greater than 0, but it will not halt</span>
<span class="hljs-comment">// because the while loop will continue to run.</span>

<span class="hljs-comment">// Copilot (April 2023)</span>
<span class="hljs-comment">// The program will not halt because the while loop will never be false. The</span>
<span class="hljs-comment">// value of a will always be greater than 0 because it is incremented by 1</span>
<span class="hljs-comment">// every time the loop runs.</span>

<span class="hljs-comment">// CodeWhisperer (July 2022)</span>
<span class="hljs-comment">// The program will halt at the first iteration.</span>
<span class="hljs-comment">// It will not halt at the second iteration.</span>
<span class="hljs-comment">// It will not halt at the third iteration.</span>

<span class="hljs-comment">// A bit ironic that CodeWhisperer will</span>
<span class="hljs-comment">// continue this "It will not halt at..." forever.</span>

<span class="hljs-comment">// CodeWhispeper (April 2023) (Doesn't work with //, only /*)</span>
<span class="hljs-comment">/*
  The program halts because the while loop is never executed.
  The while loop is never executed because the condition is never met.
*/</span>
</code></pre>
<ul>
<li><p>Copilot July 2022 is close--the last half is right.</p>
</li>
<li><p>Copilot April 2023 knocks it out of the ballpark.</p>
</li>
<li><p>CodeWhisperer April 2022 doesn't know what I'm talking about.</p>
</li>
<li><p>CodeWhisperer July 2023 is afraid to play the game with <code>//</code> but with <code>/*</code> it immediately gives a... more well-structured answer, but incorrect.</p>
</li>
</ul>
<h4 id="heading-explain-code">Explain Code</h4>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">a</span>(<span class="hljs-params">b</span>) </span>{
  <span class="hljs-keyword">return</span> b.split(<span class="hljs-string">','</span>).map(<span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> <span class="hljs-string">`&lt;b&gt;<span class="hljs-subst">${x}</span>&lt;/b&gt;`</span>).join(<span class="hljs-string">','</span>);
}
<span class="hljs-comment">// Explain what the above function does:</span>
<span class="hljs-comment">// The function</span>
</code></pre>
<pre><code class="lang-js"><span class="hljs-comment">// Copilot (July 2022)</span>
<span class="hljs-comment">// The function splits the string into an array of strings,</span>
<span class="hljs-comment">// then maps each string to a new string that is wrapped in &lt;b&gt; tags.</span>
<span class="hljs-comment">// The result is then joined back into a string with commas.</span>
<span class="hljs-comment">// The result is then returned.</span>

<span class="hljs-comment">// Copilot (April 2023)</span>
<span class="hljs-comment">// The function takes a string as input and returns a string with each word wrapped in &lt;b&gt; tags.</span>

<span class="hljs-comment">// Copilot (April 2023) with /*</span>
<span class="hljs-comment">/*
  1. Split the string by comma
  2. Map each element to a string with &lt;b&gt; tags
  3. Join the elements with comma
*/</span>

<span class="hljs-comment">// CodeWhisperer (July 2022)</span>
<span class="hljs-comment">// The function takes a string as an argument and returns a string.</span>
<span class="hljs-comment">// The string is split by the comma and then each word is wrapped in a &lt;b&gt; tag.</span>
<span class="hljs-comment">// The result is then joined together with a comma and a space.</span>
<span class="hljs-comment">// The function returns the result.</span>

<span class="hljs-comment">// CodeWhisperer (April 2023)</span>
<span class="hljs-comment">// The function takes a string and splits it into an array of strings.</span>
<span class="hljs-comment">// Then it maps each string to a string with a &lt;b&gt; tag around it.</span>
<span class="hljs-comment">// Then it joins the array of strings back together with a comma in between each string.</span>
<span class="hljs-comment">// The function returns the string.</span>
</code></pre>
<p>I appreciate CodeWhisperer July 2022's extra detail over Copilot July 2022 (it specifies that the argument is a string, what we split by, etc). However it does have a clear typo: "with a comma <strong>and a space.</strong>". It also seems to refer to comma in the singular tense both times.</p>
<p>CodeWhisperer April 2023 doesn't seem to do particularly better.</p>
<h4 id="heading-javascript-to-python">Javascript to Python</h4>
<pre><code class="lang-py"><span class="hljs-comment"># function a(b) {</span>
<span class="hljs-comment">#   return b.split(',').map(x =&gt; `&lt;b&gt;${x}&lt;/b&gt;`).join(',');</span>
<span class="hljs-comment"># }</span>
<span class="hljs-comment"># Convert the function to python:</span>
<span class="hljs-function"><span class="hljs-keyword">def</span></span>
</code></pre>
<pre><code class="lang-py"><span class="hljs-comment"># Copilot (July 2022)</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">a</span>(<span class="hljs-params">b</span>):</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">'&lt;b&gt;'</span> + b.split(<span class="hljs-string">','</span>).join(<span class="hljs-string">'&lt;/b&gt;, &lt;b&gt;'</span>) + <span class="hljs-string">'&lt;/b&gt;'</span>

<span class="hljs-comment"># Copilot (April 2023)</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">a</span>(<span class="hljs-params">b</span>):</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">','</span>.join([<span class="hljs-string">f'&lt;b&gt;<span class="hljs-subst">{x}</span>&lt;/b&gt;'</span> <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> b.split(<span class="hljs-string">','</span>)])

<span class="hljs-comment"># CodeWhisperer (July 2022)</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">a</span>(<span class="hljs-params">b</span>):</span>
    <span class="hljs-keyword">return</span> [<span class="hljs-string">f'&lt;b&gt;<span class="hljs-subst">{x}</span>&lt;/b&gt;'</span> <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> b.split(<span class="hljs-string">','</span>)]

<span class="hljs-comment"># CodeWhisperer (April 2023)</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">a</span>(<span class="hljs-params">b</span>):</span>
    <span class="hljs-keyword">return</span> b.split(<span class="hljs-string">","</span>).map(<span class="hljs-keyword">lambda</span> x: <span class="hljs-string">f"&lt;b&gt;<span class="hljs-subst">{x}</span>&lt;/b&gt;"</span>).join(<span class="hljs-string">","</span>)
</code></pre>
<ul>
<li><p>Copilot July 2022's doesn't even run in Python (<code>'list' object has no attribute 'join'</code>).</p>
</li>
<li><p>Copilot April 2023 is correct.</p>
</li>
<li><p>CodeWhisperer July 2022's is better (it runs), but it forgets to join with commas at the end.</p>
</li>
<li><p>CodeWhisperer April 2023 fails (<code>'list' object has no attribute 'map'</code>)</p>
</li>
</ul>
<h4 id="heading-sql-in-javascript">SQL in JavaScript</h4>
<pre><code class="lang-js"><span class="hljs-comment">// Get the average join date of all users</span>
<span class="hljs-comment">// on the team "Team Coco" using a SQL query.</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span></span>
</code></pre>
<pre><code class="lang-js"><span class="hljs-comment">// Copilot (July 2022)</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAverageJoinDate</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> query = <span class="hljs-string">`
    SELECT AVG(join_date)
    FROM users
    WHERE team_id = (
      SELECT id
      FROM teams
      WHERE name = 'Team Coco'
    )
  `</span>;
  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> db.query(query);
  <span class="hljs-keyword">return</span> result.rows[<span class="hljs-number">0</span>].avg;
}

<span class="hljs-comment">// Copilot (April 2023)</span>
<span class="hljs-comment">// For some reason Copilot often refuses to do this one. If you go line by line, it quickly gets stuck. After a couple attempts, I managed to get the following (line-by-line). Seems to be cut off after "f"--in the past, when something is cut off, it's usually GitHub censoring something sensitive (like an email). No idea why it's triggered here.</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAverageJoinDate</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> f
    .query(
      <span class="hljs-string">`SELECT AVG(join_date) FROM users WHERE team = "Team Coco"`</span>
    )
    .run();
  <span class="hljs-keyword">return</span> res[<span class="hljs-number">0</span>][<span class="hljs-string">"AVG(join_date)"</span>];
}

<span class="hljs-comment">// CodeWhisperer (July 2022)</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAvgJoinDate</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [rows] = <span class="hljs-keyword">await</span> connection.execute(
    <span class="hljs-string">`SELECT AVG(joindate) AS avg_joindate
    FROM users
    WHERE teamname = 'Team Coco';`</span>,
  );
  <span class="hljs-keyword">return</span> rows;
}

<span class="hljs-comment">// CodeWhisperer (April 2023), 1st suggestion</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">averageJoinDate</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// TODO</span>
}

<span class="hljs-comment">// CodeWhisperer (April 2023), 2nd suggestion</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAverageJoinDate</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> your code here</span>
  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> client.query(<span class="hljs-string">`
    SELECT avg(joindate)
    FROM users
    WHERE teamname = 'Team Coco';
  `</span>);

  <span class="hljs-keyword">return</span> result.rows[<span class="hljs-number">0</span>].avg;
}
</code></pre>
<p>Given both CodeWhisperers are technically right (the best kind of right), I added an extra comment to see how it (and Copilot) would react (<code>// Tables: "users" and "teams"</code>):</p>
<pre><code class="lang-js"><span class="hljs-comment">// Get the average join date of all users</span>
<span class="hljs-comment">// on the team "Team Coco" using a SQL query.</span>
<span class="hljs-comment">// Tables: "users" and "teams"</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span></span>
</code></pre>
<pre><code class="lang-js"><span class="hljs-comment">// Copilot (July 2022)</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAverageJoinDateOfTeamCoco</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> query = <span class="hljs-string">`
    SELECT AVG(users.join_date)
    FROM users
    JOIN teams ON users.team_id = teams.id
    WHERE teams.name = 'Team Coco'
  `</span>;
  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> db.query(query);
  <span class="hljs-keyword">return</span> result.rows[<span class="hljs-number">0</span>].avg;
}

<span class="hljs-comment">// Copilot (April 2023)</span>
<span class="hljs-comment">// Again, can generate a query just fine but is struggling to actually execute...</span>

<span class="hljs-comment">// CodeWhisperer (July 2022)</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAverageJoinDate</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [results] = <span class="hljs-keyword">await</span> client.query(<span class="hljs-string">`
    SELECT AVG(users.date_joined)
    FROM users
    JOIN teams
    ON users.team_id = teams.id
    WHERE teams.name = 'Team Coco';
  `</span>);

  <span class="hljs-keyword">return</span> results.rows[<span class="hljs-number">0</span>].avg;
}

<span class="hljs-comment">// CodeWhisperer (April 2023), again 1st suggestion is just "todo" so 2nd suggestion:</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">averageJoinDate</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// YOUR CODE BELOW HERE //</span>
  <span class="hljs-keyword">let</span> average = <span class="hljs-keyword">await</span> db.query(<span class="hljs-string">`
  SELECT avg(created_at) as average
  FROM users
  JOIN teams ON users.team_id = teams.id
  WHERE teams.name = 'Team Coco'
  `</span>);

  <span class="hljs-keyword">return</span> average.rows[<span class="hljs-number">0</span>].average;


  <span class="hljs-comment">// YOUR CODE ABOVE HERE //</span>
}
</code></pre>
<p>Both Copilot and CodeWhisperer July 2022 good. Notable differences are function names, and CodeWhisperer's odd destructuring (<code>const [results]</code> instead of just <code>const results</code>). I honestly don't super like CodeWhisperer April 2023's solution (not as prettily formatted, includes weird "YOUR CODE BELOW HERE" comments, and names the variable <code>average</code> when it isn't actually the average yet).</p>
<h3 id="heading-additional-differences">Additional Differences</h3>
<h4 id="heading-speed">Speed</h4>
<p>Copilot not only runs faster (0.5s-1s, sometimes up to ~3s), but automatically runs. CodeWhisperer requires you to hit <code>Opt-C</code> to autocomplete, and tends to load slower (usually 1-2s, sometimes up to ~5s).</p>
<p>CodeWhispeper April 2023 might be a bit faster now, but it still requires the <code>Opt-C</code> command (couldn't find a way to have it go automatically).</p>
<h4 id="heading-languages">Languages</h4>
<p>Copilot has been trained on dozens of languages and theoretically has no language limitation (although it will assuredly perform better in some than others). CodeWhisperer seems to be limited to JavaScript, Python, and Java (it refuses to autocomplete if you have another language selected in VSCode).</p>
<p>CodeWhisperer April 2023 now supports: Python, Java, JavaScript, TypeScript, C#, Rust, Go, Ruby, Scala, Kotlin, PHP, C, C++, Shell, SQL. However, it still refuses to autocomplete in VSCode languages that it does not support (Github Copilot will happily autocomplete in a plain text file).</p>
<h1 id="heading-final-thoughts">Final Thoughts</h1>
<p>I personally think Github Copilot is still further ahead, however:</p>
<ul>
<li><p><s>Amazon CodeWhisperer is not officially released.</s></p>
</li>
<li><p>They are still surprisingly similar in many circumstances.</p>
</li>
<li><p>Competition is good!</p>
</li>
</ul>
<p>(But Copilot is a cooler name than CodeWhisperer.)</p>
]]></content:encoded></item><item><title><![CDATA[How I made database joins type-safe in TypeScript.]]></title><description><![CDATA[Context: What is an ORM?
An ORM (Object-relational Mapper) is a tool that adds a layer of abstraction on to databases. For example, if we had a "team" table in a SQL database and wanted to fetch a team with TypeScript, we could do the following:
cons...]]></description><link>https://blog.lucas-simon.com/type-safe-database-joins-in-typescript</link><guid isPermaLink="true">https://blog.lucas-simon.com/type-safe-database-joins-in-typescript</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[Databases]]></category><category><![CDATA[orm]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[SQL]]></category><dc:creator><![CDATA[Lucas Simon]]></dc:creator><pubDate>Fri, 08 Jul 2022 21:08:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/07rYS9_iEkc/upload/v1656717889370/Z9xl0-i44.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-context-what-is-an-orm">Context: What is an ORM?</h2>
<p>An ORM (Object-relational Mapper) is a tool that adds a layer of abstraction on to databases. For example, if we had a "team" table in a SQL database and wanted to fetch a team with TypeScript, we could do the following:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> client.query(<span class="hljs-string">'SELECT * FROM team WHERE id = $1'</span>, id);
<span class="hljs-built_in">console</span>.log(res.rows[<span class="hljs-number">0</span>]?.name)
</code></pre>
<p>And an ORM (such as TypeORM) will automatically generate and execute the query for us:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> team = <span class="hljs-keyword">await</span> teamRepo.findOne({
  where: { id }
});
<span class="hljs-built_in">console</span>.log(team?.name);
</code></pre>
<p>While the above example may not look any simpler than the manual query, it gets a lot more helpful when your queries are more complex, such as if you want to use joins. In TypeORM, you can use the <code>relations</code> property to automatically perform the joins:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> team = <span class="hljs-keyword">await</span> teamRepo.findOne({
  where: { id },
  <span class="hljs-comment">// This automatically left joins the correct tables</span>
  <span class="hljs-comment">// and stores the values in team.members and team.members[i].user</span>
  relations: [<span class="hljs-string">'members'</span>, <span class="hljs-string">'members.user'</span>]
});

<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> member <span class="hljs-keyword">of</span> team.members) {
  <span class="hljs-built_in">console</span>.log(member.user.username);
}
</code></pre>
<p>Notice how the ORM automatically populates the <code>team</code> object with all the information we need.</p>
<h2 id="heading-the-problem-types-when-joining">The Problem: Types when joining</h2>
<p>It's extremely important to try and get things to crash at build time rather than at run time. One way we can do this is by ensuring our types are always correct. Then, our type-system will let us know if there's a case we're not handling. Unfortunately, the above example has some incorrect types.</p>
<p>The problem arises when we want to start accessing <code>team.members</code>. When we define the <code>Team</code> class, <code>members</code> must be either always optional or always required, depending on how you've defined it:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">class</span> Team {
  <span class="hljs-comment">// Other columns here</span>

  <span class="hljs-comment">// Should it be optional?</span>
  members?: Member[];
  <span class="hljs-comment">// Or always available?</span>
  members: Member[];
}
</code></pre>
<p>TypeORM will populate <code>members</code>, like you saw above, only if we do <code>relations: ['members']</code>. Otherwise, the <code>members</code> property will be undefined. So, because <code>members</code> could be undefined, we should make it optional, right?</p>
<h3 id="heading-if-members-is-optional">If <code>members</code> is optional</h3>
<p>If <code>members</code> is optional, our entire codebase becomes a hassle every single time we use relations:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> team = ...

<span class="hljs-keyword">if</span> (team.members) {
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> member <span class="hljs-keyword">of</span> team.members) {
    <span class="hljs-keyword">if</span> (member.user) {
      <span class="hljs-comment">// Well this sucks</span>
      <span class="hljs-built_in">console</span>.log(member.user.username)
    }
  }
}
</code></pre>
<p>Or we choose to ignore them using <code>!</code>, but this can be very risky. </p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> team = teamRepo.findOne({
  where: { id },
});

<span class="hljs-comment">// Yay! Our code is reduced...</span>
<span class="hljs-comment">// But wait, we forgot the relations above.</span>
<span class="hljs-comment">// No type error, but we're going to crash here.</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> member <span class="hljs-keyword">of</span> team.members!) {
  <span class="hljs-built_in">console</span>.log(member.user!.username);
}
</code></pre>
<p>Also, what if we have a function that should take in a team with members?</p>
<pre><code class="lang-ts"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">printTeamInfo</span>(<span class="hljs-params">team: Team</span>) </span>{
  <span class="hljs-comment">// This is risky, since anyone who calls this function</span>
  <span class="hljs-comment">// might not be aware that it requires the `team.members` relation</span>
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> member <span class="hljs-keyword">of</span> team.members!) {}
}
</code></pre>
<h3 id="heading-if-members-is-always-available">If <code>members</code> is always available</h3>
<p>If <code>members</code> isn't optional, we can access the property even if we haven't added the relation. This is even more unsafe, as I'm not given an error that I can ignore with <code>!</code>; I just have to <em>remember</em> to add the relation.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> team = teamRepo.findOne({
  where: { id },
});

<span class="hljs-comment">// Not a single build error, and we're not</span>
<span class="hljs-comment">// even using a ! this time.</span>
<span class="hljs-comment">// Crash at runtime, undefined (team.members) is not iterable </span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> member <span class="hljs-keyword">of</span> team.members) {
  <span class="hljs-built_in">console</span>.log(member.user.username);
}
</code></pre>
<p>In the above example, we <em>want</em> a build error to tell us that we can't use <code>team.members</code> unless we join it (by doing <code>relations: ['members']</code> when fetching the team).</p>
<h2 id="heading-possible-solution-deeprequired">Possible Solution: DeepRequired</h2>
<p>We could use the <code>as</code> keyword to apply some kind of <code>DeepRequired</code> to <code>Team</code>. However, this makes every single property required, even if we didn't add it to the relation:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> team = teamRepo.findOne({
  where: { id },
});

<span class="hljs-comment">// Yay, error as expected because we didn't join `members`</span>
<span class="hljs-built_in">console</span>.log(team.members[<span class="hljs-number">0</span>]);

<span class="hljs-comment">// // // // // // // // //</span>

<span class="hljs-keyword">const</span> teamWithMembers = teamRepo.findOne({
  where: { id },
  relations: [<span class="hljs-string">'members'</span>]
}) <span class="hljs-keyword">as</span> DeepRequired&lt;Team&gt;; <span class="hljs-comment">// We added this `as` part.</span>

<span class="hljs-comment">// Yay, no error</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> member <span class="hljs-keyword">of</span> teamWithMembers.members) {
  <span class="hljs-comment">// Uh oh. This is going to crash because</span>
  <span class="hljs-comment">// "members.user" isn't in our relation.</span>
  <span class="hljs-comment">// But there's no type error!</span>
  <span class="hljs-built_in">console</span>.log(member.user.username);
}
</code></pre>
<p>In the above example, when we didn't add any relations, we correctly got an error that <code>team.members</code> could be undefined. And when we added the relation and <code>DeepRequired</code> type, we were correctly able to loop through <code>teamWithMembers</code>. However, <code>DeepRequired</code> applies to every single property, so we got no type error when doing <code>member.user.username</code>, even though at runtime, <code>member.user</code> will be undefined!</p>
<h2 id="heading-full-solution-makerequired">Full Solution: MakeRequired</h2>
<p>We want to only make certain properties required and not others. We can do that if we write a type called <code>MakeRequired</code> (feel free to propose a better name).</p>
<p>The way <code>MakeRequired</code> works is that it takes in your type and an object that represents which properties should be defined. Here's an example:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">interface</span> Order {
  id: <span class="hljs-built_in">number</span>;
  customer?: Customer;
  items?: Item[];
}

<span class="hljs-keyword">interface</span> Customer {
  name: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">interface</span> Item {
  price?: <span class="hljs-built_in">number</span>;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getTotalOrderPrice</span>(<span class="hljs-params">
  <span class="hljs-comment">// Here!</span>
  <span class="hljs-comment">// We're saying that Order will have order.items</span>
  <span class="hljs-comment">// and order.items[i].price defined.</span>
  order: MakeRequired&lt;Order, {
    items: { price: {} }
  }&gt;
</span>) </span>{
  <span class="hljs-comment">// This will fail if uncommented because</span>
  <span class="hljs-comment">// order.customer is still undefined</span>
  <span class="hljs-comment">// console.log(order.customer.name);</span>

  <span class="hljs-comment">// Normally this would fail because</span>
  <span class="hljs-comment">// order.items could be undefined.</span>
  <span class="hljs-comment">// But with MakeRequired, we're all good!</span>
  <span class="hljs-keyword">return</span> order.items
    .reduce(
      <span class="hljs-comment">// Normally this would fail because</span>
      <span class="hljs-comment">// item.price could be undefined</span>
      (total, item) =&gt; total + item.price,
      <span class="hljs-number">0</span>,
    );
}
</code></pre>
<p>So, in order to solve our database problem, we can do:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> team = teamRepo.findOne({
  where: { id },
  relations: [<span class="hljs-string">'members'</span>]
}) <span class="hljs-keyword">as</span> MakeRequired&lt;Team, { members: {} }&gt;;

<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> member <span class="hljs-keyword">of</span> team.members) {
  <span class="hljs-comment">// Finally, just an error here!</span>
  <span class="hljs-built_in">console</span>.log(member.user.username);
}
</code></pre>
<p><code>MakeRequired</code> still respects <code>null</code> when you join a nullable value. For example, let's say we want to add a <code>leaderMember</code> property to the team. We'll make it optional with <code>?</code> because we have to join to include it, but we'll also add <code>| null</code> because it's <code>nullable</code> in the database.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">interface</span> Team <span class="hljs-keyword">extends</span> OldTeam {
  leaderMember?: Member | <span class="hljs-literal">null</span>; <span class="hljs-comment">// Member, null, or undefined</span>
}

<span class="hljs-keyword">const</span> team = teamRepo.findOne({
  where: { id },
  relations: [<span class="hljs-string">'leaderMember'</span>]
}) <span class="hljs-keyword">as</span> MakeRequired&lt;Team, { leaderMember: {} }&gt;;

<span class="hljs-comment">// Because of MakeRequired, team.leaderMember</span>
<span class="hljs-comment">// is now only either a Member or null.</span>

<span class="hljs-keyword">if</span> (team.leaderMember !== <span class="hljs-literal">null</span>) {
  <span class="hljs-built_in">console</span>.log(team.leaderMember.id);
  <span class="hljs-comment">// Without the use of `MakeRequired`, the above line</span>
  <span class="hljs-comment">// would throw an error because `team.leaderMember`</span>
  <span class="hljs-comment">// could be undefined.</span>
}
</code></pre>
<p>It's important that <code>undefined</code> means it hasn't been fetched from the database, and <code>null</code> means that it has no value in the database. If, in the above example, we simply used <code>if (team.leaderMember)</code>, it may have achieved a similar effect, however it can cause issues:</p>
<ul>
<li><code>if (team.leaderMember)</code> does not clearly communicate if we're checking if it's null, if we're checking that it was fetched from the database, or both</li>
<li>If we used <code>if (team.leaderMember)</code>, we wouldn't receive any errors if we forgot to join the <code>leaderMember</code> column (even if the type is correctly <code>Team</code> or <code>MakeRequired&lt;Team, {}&gt;</code>, which are both the same thing). This might cause the bug "why does the <code>leaderMember</code> username never get printed?", which would've been solved at build time if we used <code>team.leaderMember !== null</code> and <code>MakeRequired</code>.</li>
</ul>
<h3 id="heading-makerequired-as-function-parameters">MakeRequired as function parameters</h3>
<p>We can even ensure that functions only accept teams that have <code>leaderMember.user</code> defined:</p>
<pre><code class="lang-ts"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">doSomething</span>(<span class="hljs-params">
  team: MakeRequired&lt;Team, { leaderMember: { user: {} } }&gt;
</span>) </span>{
  <span class="hljs-comment">// No build errors, no runtime errors :D</span>
  <span class="hljs-built_in">console</span>.log(team.leaderMember.user.username);
}

<span class="hljs-comment">// Fail, missing properties</span>
doSomething(team <span class="hljs-keyword">as</span> Team);

<span class="hljs-comment">// Fail, we have leaderMember but not leaderMember.user</span>
doSomething(team <span class="hljs-keyword">as</span> MakeRequired&lt;Team, {
  leaderMember: {}
});

<span class="hljs-comment">// Success, we have the required properties</span>
doSomething(team <span class="hljs-keyword">as</span> MakeRequired&lt;Team, {
  leaderMember: { user: {} }
});

<span class="hljs-comment">// Success, we have the required properties</span>
<span class="hljs-comment">// plus some extras.</span>
doSomething(team <span class="hljs-keyword">as</span> MakeRequired&lt;Team, {
  leaderMember: { user: { profile: {} } }
  members: { user: {} }
});
</code></pre>
<p>This means you can use <code>MakeRequired</code> both when fetching from the database, but also across your codebase to maintain type-safety.</p>
<h2 id="heading-make-it-even-safer">Make it even safer</h2>
<p>The above solution also works for manual queries, however if you <em>are</em> using something like TypeORM, you can go one step further. You could make the mistake of mismatching types:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> team = teamRepo.findOne({
  where: { id },
  <span class="hljs-comment">// Uh oh! We claim we have `leaderMember.user` in the type,</span>
  <span class="hljs-comment">// but we don't include it in our relations!</span>
  relations: [<span class="hljs-string">'leaderMember'</span>]
}) <span class="hljs-keyword">as</span> MakeRequired&lt;Team, { leaderMember: { user: {} } }&gt;;
</code></pre>
<p>We can use a trick to fix this, by defining both the relations and type at once:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> team = <span class="hljs-keyword">await</span> getTeamById(teamId, {
  members: { user: {} }
});

<span class="hljs-keyword">if</span> (team) {
  <span class="hljs-comment">// No errors here at all!</span>
  <span class="hljs-keyword">const</span> teamUsernames = team.members
    .map(<span class="hljs-function">(<span class="hljs-params">member</span>) =&gt;</span> member.user.username)
    .join(<span class="hljs-string">', '</span>);
}

<span class="hljs-comment">// How we did it:</span>

<span class="hljs-keyword">type</span> PromiseTeam&lt;D&gt; = <span class="hljs-built_in">Promise</span>&lt;MakeRequired&lt;Team, D&gt; | <span class="hljs-literal">undefined</span>&gt;;

<span class="hljs-keyword">async</span> getTeamById&lt;D <span class="hljs-keyword">extends</span> Depth&lt;Team&gt; = {}&gt;(
    id: <span class="hljs-built_in">string</span>,
    depth?: D,
): PromiseReport&lt;T&gt; {
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.repository.findOne(
    { id },
    {
      <span class="hljs-comment">// We're using depth both as value...</span>
      relations: getRelations(depth),
    },
  <span class="hljs-comment">// ...and as a type!</span>
  ) <span class="hljs-keyword">as</span> PromiseTeam&lt;D&gt;;
}

<span class="hljs-comment">// Depth and getRelations can be found in the gists below</span>
</code></pre>
<h2 id="heading-lets-see-it">Let's see it!</h2>
<p>Now, for only $9.99/month... just kidding, here are the gists:</p>
<p><code>MakeRequired</code>: https://gist.github.com/demipixel/b14a6981dff823fda2ba1f1879d1446f</p>
<p><code>getRelations</code> (from example above): https://gist.github.com/demipixel/6d92789db75b40eff9bbc514d12fa6b4</p>
<p>Cheers to <code>Robinlemon#0001</code> on the <a target="_blank" href="https://discord.com/invite/typescript">official TypeScript discord</a> who helped me put this together a couple years ago :)</p>
]]></content:encoded></item><item><title><![CDATA[Dynamic Link Previews in React (Without SSR)]]></title><description><![CDATA[Intro
While working on my website, I got sick of the default link previews. Google is pretty good about running JavaScript to figure out what's on the page, but many platforms such as Facebook, Twitter, and Discord won't. And, it's not very helpful t...]]></description><link>https://blog.lucas-simon.com/dynamic-link-previews-in-react-without-ssr</link><guid isPermaLink="true">https://blog.lucas-simon.com/dynamic-link-previews-in-react-without-ssr</guid><category><![CDATA[React]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[nginx]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[performance]]></category><dc:creator><![CDATA[Lucas Simon]]></dc:creator><pubDate>Thu, 09 Dec 2021 20:09:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/v6Uko9VA-18/upload/v1638483182058/QsL23J8y8.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-intro">Intro</h2>
<p>While working on my website, I got sick of the default link previews. Google is pretty good about running JavaScript to figure out what's on the page, but many platforms such as Facebook, Twitter, and Discord won't. And, it's not very helpful to users:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1637642319822/gxvUyDcwc.png" alt="capture (5).png" /></p>
<p>When using something like Create React App, this becomes hard to change. You're likely making a <code>fetch</code> or <code>axios</code> request to <code>api.your-site.com</code> or <code>your-site.com/api</code> to fetch content, so your <code>index.html</code> is just static:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>My Website<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span>
      <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span>
      <span class="hljs-attr">content</span>=<span class="hljs-string">"A default description that is the same for every single page that a user links."</span>
    /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
</code></pre>
<h2 id="heading-using-server-side-rendering-ssr">Using Server Side Rendering (SSR)</h2>
<p>Yes, SSR could solve this, and there are plenty of benefits to using something like Next.js. But for an existing project, it can be a big undertaking to convert to Next.js, especially when working with dozens of pages, each with their own unique fetches.</p>
<p>Using new technology for a large project also has some inherent risks. For example, one of my pages requires multiple requests to determine the title:</p>
<blockquote>
<p>NA West Duos Queue ($50 in prizes)</p>
</blockquote>
<p><em>("NA West" and "Duos" is determined from the queue endpoint, however the $50 is determined from the ladders endpoint.)</em></p>
<p>Using Next.js, I would need to force users to wait for those internal requests to complete before I could even start responding to their request. Furthermore, if that specific API is slow or down, we have some edge cases we need to handle (How long do we wait for that service to respond? What do we show the user if it fails? If only one request fails, can we partially display the data from the other request?).</p>
<p><strong>This is just one example of the problems I would encounter when transitioning to Next.js—and there are more that I wouldn't find out about until the implementation was complete.</strong></p>
<h2 id="heading-basic-solution-with-node">Basic Solution With Node</h2>
<p>Luckily, we don't need SSR to do this. While not optimal, this problem can quickly be solved with an express server:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> indexFile = fs.readFileSync(<span class="hljs-string">'./index.html'</span>, <span class="hljs-string">'utf-8'</span>);

app.get(<span class="hljs-string">'*'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  getTitleAndDescription(req)
    .then(<span class="hljs-function">(<span class="hljs-params">{ title, desc }</span>) =&gt;</span> {
      res.send(
        indexFile
          .replace(
            <span class="hljs-string">'&lt;title&gt;My Website&lt;/title&gt;'</span>,
            <span class="hljs-string">`&lt;title&gt;<span class="hljs-subst">${title}</span>&lt;/title&gt;`</span>,
          )
          .replace(DEFAULT_DESCRIPTION, desc || DEFAULT_DESCRIPTION),
      );
    })
    .catch(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
      res.send(indexFile);
      <span class="hljs-built_in">console</span>.error(e);
    });
});
</code></pre>
<p><code>getTitleAndDescription(req)</code> is a function that can do anything, including making requests to your API to figure out what to render. However, this has two negatives:</p>
<ol>
<li>Nginx is written in C. It's just always going to be more performant and use less resources per request than Node.</li>
<li>We're increasing the load on our API by making additional requests every time a user loads the page. In addition, load time will be increased (depending on your caching situation).</li>
</ol>
<h2 id="heading-combining-with-nginx">Combining With Nginx</h2>
<p>I was using nginx at the time and wanted to continue doing so, so I ended up merging the solutions. This is how I was previously serving my <code>index.html</code>:</p>
<pre><code><span class="hljs-attribute">location</span> / {
    <span class="hljs-attribute">try_files</span> <span class="hljs-variable">$uri</span> <span class="hljs-variable">$uri</span>/ /index.html;
    <span class="hljs-comment"># Add some headers here too...</span>
}
</code></pre><p>And after my change:</p>
<pre><code>map $http_user_agent $is_bot {
    <span class="hljs-keyword">default</span> <span class="hljs-number">0</span>;
    ~*facebook|crawl|slurp|spider|bot|tracker|click|parser <span class="hljs-number">1</span>;
}

<span class="hljs-comment"># Later...</span>

location / {
    try_files $uri $uri/ /index.html;
    <span class="hljs-comment"># Add headers...</span>

    <span class="hljs-keyword">if</span> ($is_bot) {
        proxy_pass http:<span class="hljs-comment">//localhost:3012;</span>
    }
}
</code></pre><p>Here, if the user agent matches the regex (e.g. contains <code>facebook</code>, <code>crawl</code>, <code>bot</code>, etc), we'll instead forward the request to our node server. While a user could spoof their user agent, this isn't really a security issue since we're not serving any sensitive information. We don't need to do any of the work generating the description for the user, and we can always set the title later for them dynamically.</p>
<p>I was using Kubernetes and was able to define a single deployment with two images. However, you could do this with simple docker containers (or just barebones on a simple server).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1637646220612/uHQr0VHAI.png" alt="Explain.png" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>That was my solution: Create a node server specifically designed for requests from bots. While this might not be the best solution for everyone, I wanted to share my thought process. I may still convert my application over to Next.js at some point in the future, but it wasn't the right time when I wanted to implement this feature, and there were less-risky alternatives available. Adding new technology to a large application can be a big risk, so it's important to consider your options thoroughly. Oh, and also, if people are ever linking your stuff, add link previews. They're awesome!</p>
]]></content:encoded></item><item><title><![CDATA[How I took down my site and then fixed it with MemoLock.]]></title><description><![CDATA[TL;DR
When you do simple reads/writes to a cache, you risk having a ton of database queries when your cache expires (which can have disastrous effects). With MemoLock, only one process fetches while every other process waits. You can find a Redis Mem...]]></description><link>https://blog.lucas-simon.com/how-i-took-down-my-site-and-fixed-it-with-memolock</link><guid isPermaLink="true">https://blog.lucas-simon.com/how-i-took-down-my-site-and-fixed-it-with-memolock</guid><category><![CDATA[caching]]></category><category><![CDATA[Redis]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Databases]]></category><dc:creator><![CDATA[Lucas Simon]]></dc:creator><pubDate>Fri, 12 Nov 2021 19:56:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1636746225987/5OZSFMy4N.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="tldr">TL;DR</h2>
<p>When you do simple reads/writes to a cache, you risk having a ton of database queries when your cache expires (which can have disastrous effects). With MemoLock, only one process fetches while every other process waits. You can find a Redis MemoLock implementation for C# and Go <a target="_blank" href="https://github.com/kristoff-it/redis-memolock">here</a>, and my Node/TypeScript implementation (which is fully tested and used in production) <a target="_blank" href="https://github.com/demipixel/redis-memolock-node">here</a>.</p>
<h2 id="story-time">Story Time</h2>
<p>The website I work on allows users to join a queue.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636580549340/6-qmsmdrG.png" alt="Screen Shot 2021-11-10 at 1.35.37 PM.png" /></p>
<p>Initially, when a user loaded the "queue page", we fetched how many users were in the queue:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">count</span>(*) <span class="hljs-keyword">FROM</span> que_user <span class="hljs-keyword">WHERE</span> <span class="hljs-string">"queId"</span> = $<span class="hljs-number">1</span> <span class="hljs-keyword">AND</span> <span class="hljs-string">"status"</span> &lt; $<span class="hljs-number">2</span>
</code></pre>
<p><code>O(n)</code> latency is manageable, since when you 4x your load, things slow down 4x, and you have time to improve it. However, when latency becomes <code>O(n^2)</code> (4x load -&gt; 16x latency), issues can arise seemingly overnight.</p>
<h3 id="one-day">One day...</h3>
<p>One day, we had 5,500 users in the queue. Yay! Everything leading up to the event seems fine. We're only able to take a limited number of users from the queue at a time (let's say 500), so the event starts and 500 users are taken in the first wave. This is where things go wrong.</p>
<p>Nothing is loading. Whether it be because users are confused that they weren't in the first wave or something else, users begin to reload the page. I might not have noticed this before because, if this happens with 1,000 users, each request only counts 1,000 rows in the database. Postgres seemed to manage counting 1M total rows just fine. With 5,000, that's counting ~25M rows. Database is not happy. Query timeouts everywhere. People refresh multiple times because things aren't loading. Load increases even more.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636588638089/3md-2xcBP.jpeg" alt="Bad.jpg" /></p>
<h3 id="just-cache-it-right">Just cache it... right?</h3>
<p>Indeed, the immediate solution is to cache. After I discovered the root cause, I added a cache with a 2 second expiration. Users might not get the <em>exact</em> count, but it won't be off by very much. (This could be improved by incrementing/decrementing the cache when a user joins or leaves to make it more accurate.)</p>
<p>This does technically have a race condition where there will be multiple requests when the cache expires. That's not a big deal, so long as we're fast enough to populate the cache... But what if <em>another</em> query slows down the database?</p>
<h3 id="a-couple-months-later">A couple months later</h3>
<p>On one fateful day, another query causes high database load. Our "count query" starts timing out, but that means <strong>the cache is never populated</strong>. Suddenly, we're making even more database requests than before, which only contributes to the problem.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636582633316/dkUEm3wuc.png" alt="Screen Shot 2021-11-10 at 2.10.41 PM.png" /></p>
<h2 id="memolock">Memolock</h2>
<p>The answer is memolock. <a target="_blank" href="https://redis.com/blog/caches-promises-locks/">Redis</a> and <a target="_blank" href="https://instagram-engineering.com/thundering-herds-promises-82191c8af57d">Instagram</a> have both written articles regarding this issue. The solution is rather simple: One process fetches, and everybody else waits for the cache to be populated. When things get slow, only one SQL request will go through at a time. The actual implementation is trickier because you need to handle locks timing out, processes crashing, and missed Redis pub/sub messages.</p>
<p>Luckily, some libraries exist. You can find an implementation with Redis for C# and Go <a target="_blank" href="https://github.com/kristoff-it/redis-memolock">here</a>, and my Node/TypeScript implementation (which is fully tested and used in production) <a target="_blank" href="https://github.com/demipixel/redis-memolock-node">here</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636746515102/p-jsJxLOV.png" alt="Screen Shot 2021-11-12 at 11.48.13 AM.png" />
<em>Cache all the things. Then cache harder.</em></p>
<h2 id="conclusion">Conclusion</h2>
<p>That's MemoLock, the next step on your caching journey. With a library, you don't even need to think about the locking. The overhead is low, so even if you don't need it, you're only making a couple additional Redis queries when populating the cache. If you do need it... you might be sorry that you don't have it. </p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> MemolockCache <span class="hljs-keyword">from</span> <span class="hljs-string">'redis-memolock'</span>;

<span class="hljs-keyword">const</span> cache = <span class="hljs-keyword">new</span> MemolockCache();

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getUsersInQueCount</span>(<span class="hljs-params">queId: <span class="hljs-built_in">string</span></span>) </span>{
  <span class="hljs-keyword">return</span> cache.get(
    <span class="hljs-comment">// Key</span>
    <span class="hljs-string">'que-user-count:'</span> + queId,

    <span class="hljs-comment">// Cache for 5 seconds</span>
    { ttlMs: <span class="hljs-number">5</span> * <span class="hljs-number">1000</span> },

    <span class="hljs-comment">// When not cached, count # of users in a</span>
    <span class="hljs-comment">// queue with a database query.</span>
    () =&gt; countQuersWithDb(queId),
  );
}
</code></pre>
<p><a target="_blank" href="https://github.com/demipixel/redis-memolock-node">Click here to see more examples here.</a></p>
]]></content:encoded></item><item><title><![CDATA[Code Readability: Constraints]]></title><description><![CDATA[A few days ago, I made a joke post regarding the recent Missouri teacher SSN leak. Some proposed that await would have been a better alternative to chaining .then, and that inspired this article about writing clean code.
The Code
Just gloss over thes...]]></description><link>https://blog.lucas-simon.com/code-readability-constraints</link><guid isPermaLink="true">https://blog.lucas-simon.com/code-readability-constraints</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[clean code]]></category><category><![CDATA[promises]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Lucas Simon]]></dc:creator><pubDate>Fri, 22 Oct 2021 10:14:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1634517425097/u1-SqxHFE.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A few days ago, I made a <a target="_blank" href="https://redd.it/q9kxu2">joke post</a> regarding the recent <a target="_blank" href="https://apnews.com/article/technology-education-hacking-missouri-newspapers-6f730cb3ac61ad4cf120e7702a789d6b">Missouri teacher SSN leak</a>. Some proposed that <code>await</code> would have been a better alternative to chaining <code>.then</code>, and that inspired this article about writing clean code.</p>
<h2 id="the-code">The Code</h2>
<p>Just gloss over these two functions.</p>
<pre><code><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getRandomNumber</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> fetch(<span class="hljs-string">'https://www.missouri-educator-portal.com/listing'</span>)
    .then(<span class="hljs-function"><span class="hljs-params">r</span> =&gt;</span> r.text())
    .then(<span class="hljs-function"><span class="hljs-params">text</span> =&gt;</span> {
        <span class="hljs-keyword">const</span> SSNs = [...text.matchAll(<span class="hljs-regexp">/"ssn": "(.+)"/g</span>)]
                       .map(<span class="hljs-function"><span class="hljs-params">m</span> =&gt;</span> <span class="hljs-built_in">parseInt</span>(m[<span class="hljs-number">1</span>], <span class="hljs-number">10</span>));

        <span class="hljs-keyword">const</span> SSN = SSNs[<span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Math</span>.random() * SSNs.length)];

        <span class="hljs-comment">// Convert to number between 0 and 1</span>
        <span class="hljs-keyword">return</span> (SSN - <span class="hljs-number">100000000</span>)/<span class="hljs-number">899999999</span>;
    })
    <span class="hljs-comment">// Used Math.random() to generate</span>
    .catch(<span class="hljs-function">() =&gt;</span> <span class="hljs-number">0.4295110465741161</span>);
}
</code></pre><p>The equivalent code written using <code>await</code>:</p>
<pre><code><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getRandomNumber</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> resp = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://www.missouri-educator-portal.com/listing'</span>);
    <span class="hljs-keyword">const</span> text = <span class="hljs-keyword">await</span> resp.text();
    <span class="hljs-keyword">const</span> SSNs = [...text.matchAll(<span class="hljs-regexp">/"ssn": "(.+)"/g</span>)]
                    .map(<span class="hljs-function"><span class="hljs-params">m</span> =&gt;</span> <span class="hljs-built_in">parseInt</span>(m[<span class="hljs-number">1</span>], <span class="hljs-number">10</span>));

    <span class="hljs-keyword">const</span> SSN = SSNs[<span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Math</span>.random() * SSNs.length)];

    <span class="hljs-comment">// Convert to number between 0 and 1</span>
    <span class="hljs-keyword">return</span> (SSN - <span class="hljs-number">100000000</span>)/<span class="hljs-number">899999999</span>;
  } <span class="hljs-keyword">catch</span> {
    <span class="hljs-comment">// Used Math.random() to generate</span>
    <span class="hljs-keyword">return</span> <span class="hljs-number">0.4295110465741161</span>;
  }
}
</code></pre><p>(Please forgive the fact that <code>100000000</code> is not, in fact, the lowest possible SSN. My bad!)</p>
<h2 id="blink-of-an-eye">Blink of an Eye</h2>
<p>Now, in the first 2-3 seconds of reading functions like these, what do you see? For the first, it might look like:</p>
<pre><code><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getRandomNumber</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> fetch(...)
    .then(<span class="hljs-function"><span class="hljs-params">r</span> =&gt;</span> ...)
    .then(<span class="hljs-function"><span class="hljs-params">text</span> =&gt;</span> {
      <span class="hljs-comment">// ...</span>

      <span class="hljs-keyword">return</span> ...
    })
    .catch(<span class="hljs-function">() =&gt;</span> ...);
}
</code></pre><p>and the second might look like:</p>
<pre><code><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getRandomNumber</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> resp = <span class="hljs-keyword">await</span> fetch(...)
    <span class="hljs-keyword">const</span> text = <span class="hljs-keyword">await</span> ...

    <span class="hljs-comment">// ...</span>

    <span class="hljs-keyword">return</span> ...
  } <span class="hljs-keyword">catch</span> {
    <span class="hljs-keyword">return</span> ...
  }
}
</code></pre><p>Understanding the structure of a function in the first few seconds is the difference between a function "just making sense" and having to read the function word-by-word to understand what it does. Consider a couple of the issues with the second function:</p>
<pre><code>async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getRandomNumber</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Are we going to return anything in this func?</span>
  <span class="hljs-comment">// The original immediately started with "return"</span>

  <span class="hljs-comment">// I have no idea what this try is catching.</span>
  <span class="hljs-comment">// Oh, it could be the fetch! But also maybe something</span>
  <span class="hljs-comment">// later in the function...?</span>
  <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// Is resp used again? I might have to remember that resp exists</span>
    <span class="hljs-keyword">const</span> resp = await fetch(...)
    <span class="hljs-keyword">const</span> text = await ...

    <span class="hljs-comment">// ...</span>

    <span class="hljs-keyword">return</span> ...
  } <span class="hljs-keyword">catch</span> {
    <span class="hljs-keyword">return</span> ...
  }
}
</code></pre><h2 id="a-simpler-example">A Simpler Example</h2>
<p>Consider the following functions. What information can we deduce about how these functions work?</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getActiveUserIds</span>(<span class="hljs-params">articles: Article[], profiles: Profile[]</span>) </span>{
  <span class="hljs-keyword">const</span> userIds = <span class="hljs-keyword">new</span> Set&lt;<span class="hljs-keyword">string</span>&gt;();

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> article of articles) {
    <span class="hljs-keyword">if</span> (<span class="hljs-comment">/*...*/</span>) {
      <span class="hljs-comment">/*...*/</span>
    }
  }

  <span class="hljs-keyword">return</span> userIds;
}
</code></pre><p>For the above function:</p>
<ul>
<li>Return a set (although we have to look at the top <em>and</em> bottom to find out)</li>
<li>We'll be looping through articles and doing... something</li>
<li>What is that <code>profiles</code> parameter for? Is the output based on that? I'll need to read deeper to find out...</li>
<li>Is <code>userIds</code> <em>read</em> somewhere in this function? Or does it only have values added to it?</li>
</ul>
<pre><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getActiveUserIds2</span>(<span class="hljs-params">articles: Article[], profiles: Profile[]</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>(
    articles
      .filter(<span class="hljs-function">(<span class="hljs-params">article</span>) =&gt;</span> <span class="hljs-comment">/*...*/</span>)
      .map(<span class="hljs-function">(<span class="hljs-params">article</span>) =&gt;</span> <span class="hljs-comment">/*...*/</span>)
  );
}
</code></pre><p>For this function, we know we:</p>
<ul>
<li>Return a set</li>
<li>The set must have some kind of one-to-many mapping with <code>articles</code> (no article could contribute more than one value to the <code>Set</code>)</li>
<li>The above point guarantees the set size is <code>&lt;= articles.length</code></li>
<li>We still don't know what <code>profiles</code> is for, but it's clearer now that it's more of a helper variable than it is the primary way that our set is populated.</li>
</ul>
<p>If you're familiar with <code>map</code> and <code>filter</code>, it's immediately easier to tell the broader picture of the second function. These functions do the same thing, but you're able to skim less text and learn more in the second than the first. <strong>The second function is written such that it's easier to constrain your mental image of the functionality with a glance.</strong></p>
<h2 id="proposed-pipe-operator">Proposed Pipe Operator</h2>
<p>Code readability is based on how quickly and clearly you can communicate to the reader. And, while code is run top to bottom, it's not always the best way to understand it. Readers skim to get the gist of what something does. The less constrained your outline is, the more readers have to start reading word-by-word.</p>
<p>This doesn't always mean doing things functionally. For example, the new pipe operator achieves a similar effect to <code>.then</code> (<a target="_blank" href="https://redd.it/q61a0o">example shamelessly stolen</a>):</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> json = pkgs[<span class="hljs-number">0</span>]
  |&gt; npa(^).escapedName
  |&gt; <span class="hljs-keyword">await</span> npmFetch.json(^, opts)
</code></pre>
<p>which, to a computer, is equivalent to:</p>
<pre><code><span class="hljs-keyword">const</span> first = pkgs[<span class="hljs-number">0</span>]
<span class="hljs-keyword">const</span> escaped = npa(first).escapedName
<span class="hljs-keyword">const</span> json = <span class="hljs-keyword">await</span> npmFetch.json(escaped, opts)
</code></pre><p>However, for humans, it provides benefits such as less code to read, reducing the number of variable names we have to remember and guaranteeing that order of these statements matters.</p>
<h2 id="closing-remarks">Closing Remarks</h2>
<p>Code readability is always going to be balanced with other requirements (such as runtime efficiency, memory efficiency, etc). But, when possible, consider the tools you're using to solve a problem and what they communicate. In JavaScript/TypeScript a few examples might include:</p>
<ul>
<li>Use <code>.then()</code> to communicate that you're mapping a promise and don't need the previous value.</li>
<li>Use <code>.map</code>, <code>.filter</code>, and <code>.reduce</code> to communicate you're manipulating an array in a specific way and don't need the previous value.</li>
<li>Use a <code>for</code> loop if you would need to twist your code to fit in <code>.map</code>, <code>.filter</code>, or <code>.reduce</code></li>
<li>Start a function with <code>return</code> (when possible) to communicate that the function is pure (i.e. it doesn't mutate anything).</li>
<li>Use a ternary (<code>cond ? val1 : val2</code>) to communicate that only a specific part of your statement is modified based on a condition.</li>
<li>Use recursion instead of a loop (when not at risk of stack overflows) to communicate that you're running the same actions on a subset of the input.</li>
<li>Use <code>for (const a of b)</code> over <code>for (let i = 0; i &lt; b.length; i++)</code> to communicate that you don't need the index (this one is actually an <a target="_blank" href="https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-for-of.md">eslint rule</a>!)</li>
</ul>
]]></content:encoded></item></channel></rss>