<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-01-19T07:39:46+00:00</updated><id>/feed.xml</id><title type="html">right click inspect</title><subtitle>Right click inspect - ideas and opinions on technology</subtitle><entry><title type="html">Building robust helm charts</title><link href="/devops/helm/kubernetes/2026/01/17/building-robust-helm-charts.html" rel="alternate" type="text/html" title="Building robust helm charts" /><published>2026-01-17T00:00:00+00:00</published><updated>2026-01-17T00:00:00+00:00</updated><id>/devops/helm/kubernetes/2026/01/17/building-robust-helm-charts</id><content type="html" xml:base="/devops/helm/kubernetes/2026/01/17/building-robust-helm-charts.html"><![CDATA[<h1 id="building-robust-helm-charts">Building robust helm charts</h1>

<p>In my current work, there is often the need to deploy a similar application
stack in various configurations, to several environments. Each configuration may
vary in terms of scale, uptime requirements and feature flagging. Due to a lot
of flux in infrastructure set up, each environment is also not equivalent. On
top of this, there are obviously financial requirements to run all of this as
cheaply as possible. Kubernetes and helm templating are valuable tools in this
situation, they allow us to create a configuration blueprint with the details
abstracted in <code class="language-plaintext highlighter-rouge">values.yaml</code> files.</p>

<h2 id="use-helms-built-in-linter">Use helm’s built in linter</h2>

<p>Let’s start with the basics, helm provides a <code class="language-plaintext highlighter-rouge">helm lint</code> command which performs
checks</p>

<ul>
  <li>YAML syntax</li>
  <li>Template rendering</li>
  <li>Missing or misnamed required files</li>
  <li>Best practice violations</li>
</ul>

<p>You can run this with your different values.yaml files to ensure that all your
configurations are compliant.</p>

<p>It’s also a good idea to use the <code class="language-plaintext highlighter-rouge">helm template</code> command to actually check that
helm is able to render your templates.</p>

<h2 id="parallels-with-front-end-templating">Parallels with front end templating</h2>

<p>I like to compare helm templating with html templating tools like JSX. This
allows front end developers to create reusable components usable throughout
pages of a web application, A button component for example can have many states,
primary, secondary, loading, disabled, light or dark mode.
<img src="/assets/images/button-states.png" alt="Button States" style="display: block;
margin: auto;" /></p>

<p>Each state may also look different depending on the size/type of device your are
browsing the site with. Each of these states represents differences in many
parameters (font size, colour, gradient, opacity, border, padding, margin,
width, height, etc). These complexities are abstracted away giving the consuming
code the list of states to chose from, so that they can write code like this.</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nt">button</span> <span class="na">type</span><span class="p">=</span><span class="s">"primary"</span><span class="p">&gt;</span>Click Me!<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>Under the hood of course many aspects of the CSS or HTML code will be impacted
by the change of state so you often end up with different parts of the markup
having conditionals on the same check.</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">Button</span> <span class="o">=</span> <span class="p">(</span><span class="nx">props</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">(</span>
        <span class="p">&lt;</span><span class="nt">button</span> <span class="na">className</span><span class="p">=</span><span class="s">"btn btn-primary {classesForState(props.state)}"</span><span class="p">&gt;</span>
            <span class="si">{</span><span class="nx">props</span><span class="p">.</span><span class="nx">state</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">loading</span><span class="dl">"</span> <span class="o">&amp;&amp;</span> <span class="p">&lt;</span><span class="nt">span</span><span class="p">&gt;&lt;</span><span class="nt">svg</span> <span class="na">src</span><span class="p">=</span><span class="s">"loading.svg"</span> <span class="p">/&gt;&lt;/</span><span class="nt">span</span><span class="p">&gt;</span><span class="si">}</span>
            <span class="si">{</span><span class="nx">props</span><span class="p">.</span><span class="nx">children</span><span class="si">}</span>
            <span class="p">&lt;</span><span class="nt">span</span><span class="p">&gt;</span>
        <span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
    );
}
</code></pre></div></div>

<p>Just in this contrived example you already have 2 different things being
controlled by the state property with 2 separate checks, the CSS classes and the
presence of the loading icon.</p>

<p>This is quite similar to the situation you end up templating in YAML with helm.
Consider an application that has optional persistent storage. You could quite
easily imagine a boolean property in your <code class="language-plaintext highlighter-rouge">values.yaml</code> file called
<code class="language-plaintext highlighter-rouge">persistent</code>. Under the hood this has many implications likely affecting
different files.</p>

<ul>
  <li>Conditional creation of a PersistentVolume resource</li>
  <li>Conditional creation of a PersistentVolumeClaim resource</li>
  <li>Conditional storage requests/limits in your Pod</li>
  <li>Adding a <code class="language-plaintext highlighter-rouge">volumes</code> block to your Pod</li>
  <li>Adding a <code class="language-plaintext highlighter-rouge">volumesMount</code> block to your Pod</li>
</ul>

<p>That’s 5 separate <code class="language-plaintext highlighter-rouge">if</code> blocks that need to be in your templates.</p>

<p>Forgetting one of these blocks could cause your application to function
incorrectly and in this case, even cause unexpected data loss. Rather than find
these problems out post deployment we can use the output of helm template with
specific values to ensure that the right manifests are generated before going
anywhere near a kubernetes cluster.</p>

<h2 id="helm-unit-test">Helm unit test</h2>

<p>After talking about this problem with a colleague, they told me that his team
use <a href="https://github.com/helm-unittest/helm-unittest">helm unit test</a> for this.
This is a simple helm plugin that allows us to assert on the output of helm
templates using yaml tests.</p>

<p>A test for the case described above could look like this. Assuming you have your
chart templates arranged as one file per resource:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>test-chart
├── Chart.yaml
├── templates
│   ├── _helpers.tpl
│   ├── persistent-volume-claim.yaml
│   └── pod.yaml
└── values.yaml
</code></pre></div></div>

<p>You could add a test for the persistent volume and a similar one for the
persistent volume claim</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">suite</span><span class="pi">:</span> <span class="s">persistent volume suite</span>
<span class="na">templates</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">persistent-volume.yaml</span>
<span class="na">tests</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">it</span><span class="pi">:</span> <span class="s">doesn't include when persistence is disabled</span>
    <span class="na">set</span><span class="pi">:</span>
      <span class="na">persistent</span><span class="pi">:</span> <span class="no">false</span>
    <span class="na">asserts</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">hasDocuments</span><span class="pi">:</span>
          <span class="na">count</span><span class="pi">:</span> <span class="m">0</span>
  <span class="pi">-</span> <span class="na">it</span><span class="pi">:</span> <span class="s">includes when persistence is enabled</span>
    <span class="na">set</span><span class="pi">:</span>
      <span class="na">persistent</span><span class="pi">:</span> <span class="no">true</span>
    <span class="na">asserts</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">containsDocument</span><span class="pi">:</span>
          <span class="na">kind</span><span class="pi">:</span> <span class="s">PersistentVolume</span>
          <span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
</code></pre></div></div>

<p>Then you could add another test for the pod</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">suite</span><span class="pi">:</span> <span class="s">pod suite</span>
<span class="na">templates</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">pod.yaml</span>
<span class="na">tests</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">it</span><span class="pi">:</span>
      <span class="s">Sets storages limits and no volumes are added when persistence is disabled</span>
    <span class="na">set</span><span class="pi">:</span>
      <span class="na">persistent</span><span class="pi">:</span> <span class="no">false</span>
    <span class="na">asserts</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">notExists</span><span class="pi">:</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">spec.volumes</span>
      <span class="pi">-</span> <span class="na">notExists</span><span class="pi">:</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">spec.containers[0].volumeMounts</span>
      <span class="pi">-</span> <span class="na">equal</span><span class="pi">:</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">spec.containers[0].resources.requests.ephemeral-storage</span>
          <span class="na">value</span><span class="pi">:</span> <span class="s">500Mi</span>
      <span class="pi">-</span> <span class="na">equal</span><span class="pi">:</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">spec.containers[0].resources.limits.ephemeral-storage</span>
          <span class="na">value</span><span class="pi">:</span> <span class="s">1Gi</span>
  <span class="pi">-</span> <span class="na">it</span><span class="pi">:</span> <span class="s">Volume is added when persistence is enabled</span>
    <span class="na">set</span><span class="pi">:</span>
      <span class="na">persistent</span><span class="pi">:</span> <span class="no">true</span>
    <span class="na">asserts</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">lengthEqual</span><span class="pi">:</span>
          <span class="na">paths</span><span class="pi">:</span>
            <span class="pi">-</span> <span class="s">spec.volumes</span>
            <span class="pi">-</span> <span class="s">spec.containers[0].volumeMounts</span>
          <span class="na">count</span><span class="pi">:</span> <span class="m">1</span>
      <span class="pi">-</span> <span class="na">exists</span><span class="pi">:</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">spec.volumes</span>
      <span class="pi">-</span> <span class="na">exists</span><span class="pi">:</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">spec.containers[0].volumeMounts</span>
      <span class="pi">-</span> <span class="na">notExists</span><span class="pi">:</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">spec.containers[0].resources.requests.ephemeral-storage</span>
      <span class="pi">-</span> <span class="na">notExists</span><span class="pi">:</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">spec.containers[0].resources.limits.ephemeral-storage</span>
</code></pre></div></div>

<p>Your chart directory should now look like this</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>test-chart
├── Chart.yaml
├── templates
│   ├── _helpers.tpl
│   ├── persistent-volume-claim.yaml
│   └── pod.yaml
├── tests
│   ├── persistent_volume_claim_test.yaml
│   ├── persistent_volume_test.yaml
│   └── pod_test.yaml
└── values.yaml
</code></pre></div></div>

<p>You can run these tests with a single docker command which should be simple to
integrate into your CI configuration</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">-t</span> <span class="nt">--rm</span> <span class="nt">-v</span> <span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span>:/apps helmunittest/helm-unittest:3.19.0-1.0.3 test-chart
</code></pre></div></div>

<p>Now you have confidence that the templated output for persistent and non
persistent configurations is as you expect. If someone removes one of your
template conditionals, they will be warned by failing tests.</p>

<h2 id="native-helm-test">Native helm test</h2>

<p>Unit tests are all well and good, but they don’t really confirm that your chart
works correctly or even that your templated output contains valid kubernetes
manifests. This is where helm’s native test feature comes in. It allows you to
run checks on your chart after it’s been deployed to a cluster. If your chart is
for a custom built application, this could be your integration test suite, but
if it’s a deployment of some vendor application with custom configuration, this
is also a great way to check that your configuration works as expected. I find
this especially useful for things like proxy servers.</p>

<p>As a simple example, let’s say you’re deploying a proxy to handle TLS
redirection, in nginx, that would be something like</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server {
    listen 80 default_server;
    server_name _;
    return 301 https://$host$request_uri;
}
</code></pre></div></div>

<p>You could use something like <a href="https://hurl.dev/">hurl</a> to check that http
requests are indeed redirecting to their https alternatives. You can put a hurl
script in a config map.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># templates/tests/proxy-tests-config-map.yaml</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ConfigMap</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">proxy-test-requests"</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="s2">"</span><span class="s">helm.sh/hook"</span><span class="err">:</span> <span class="s2">"</span><span class="s">pre-install,pre-upgrade"</span>
    <span class="s2">"</span><span class="s">helm.sh/hook-weight"</span><span class="err">:</span> <span class="s2">"</span><span class="s">0"</span>
    <span class="s2">"</span><span class="s">helm.sh/hook-delete-policy"</span><span class="err">:</span> <span class="s">before-hook-creation</span>
<span class="na">data</span><span class="pi">:</span>
  <span class="na">tests.hurl</span><span class="pi">:</span> <span class="pi">|</span>
    <span class="s"># Test https redirection</span>
    <span class="s">GET http://my-proxy.my-namespace.svc/path</span>
    <span class="s">HTTP 301</span>
    <span class="s">[Asserts]</span>
    <span class="s">header "Location" == "https://my-proxy.my-namespace.svc/path"</span>
</code></pre></div></div>

<p>And then add a pod to run it</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># templates/tests/proxy-tests-pod.yaml</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Pod</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">proxy-tests</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="s2">"</span><span class="s">helm.sh/hook"</span><span class="err">:</span> <span class="s2">"</span><span class="s">test"</span>
    <span class="s2">"</span><span class="s">helm.sh/hook-weight"</span><span class="err">:</span> <span class="s2">"</span><span class="s">1"</span>
    <span class="s2">"</span><span class="s">helm.sh/hook-delete-policy"</span><span class="err">:</span> <span class="s2">"</span><span class="s">before-hook-creation"</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">containers</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">hurl</span>
      <span class="na">image</span><span class="pi">:</span> <span class="s">ghcr.io/orange-opensource/hurl:7.1.0</span>
      <span class="na">command</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">hurl"</span><span class="pi">]</span>
      <span class="na">args</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">--test"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">/tests/tests.hurl"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">--verbose"</span><span class="pi">]</span>
      <span class="na">volumeMounts</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">proxy-test-requests"</span>
          <span class="na">readOnly</span><span class="pi">:</span> <span class="no">true</span>
          <span class="na">mountPath</span><span class="pi">:</span> <span class="s">/tests</span>
  <span class="na">volumes</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">proxy-test-requests"</span>
      <span class="na">configMap</span><span class="pi">:</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">proxy-test-requests"</span>
  <span class="na">restartPolicy</span><span class="pi">:</span> <span class="s">Never</span>
</code></pre></div></div>

<p>You can also use this to perform other checks, the advantage is that you can run
these checks in the same kubernetes namespace that you deployed to giving you
real world network conditions for example.</p>

<p>You can run these right after your deploying your chart in your CI system.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  helm <span class="nb">test </span>hs-solr-migration-proxy <span class="se">\</span>
    <span class="nt">--logs</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">--logs</code> argument will output the test pod’s logs in the output of
<code class="language-plaintext highlighter-rouge">helm test</code> so you can examine failures easily and without necessarily accessing
the cluster yourself.</p>

<h2 id="generating-documentation">Generating documentation</h2>

<p>It’s also important to have human friendly documentation for your charts so that
consumers understand the various options available set in their values.yaml
files, what the defaults are and what each option does. The
<a href="https://github.com/norwoodj/helm-docs">helm-docs</a> tool, parses your chart
values and metadata to generate documentation in a README.md file. Without any
additional effort it will create a table of all the options and their default
values. You can add a description column by adding a comment above the parameter
in your values.yaml file.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># -- Saves application data to a persistent volume surviving application restarts</span>
<span class="na">persistent</span><span class="pi">:</span> <span class="no">true</span>
</code></pre></div></div>

<p>helm-docs also supply a <a href="https://pre-commit.com/">pre-commit</a> configuration
which you can use to automatically regenerate the documentation when the chart
changes which helps keep it in sync.</p>

<h2 id="full-pipeline">Full pipeline</h2>

<p>To summarise, if we combine all the things we’ve discussed in this post, your
workflow for creating a robust helm chart might look like this.</p>

<ol>
  <li>Make chart changes annotating your values file with comments to be consumed
by helm-docs</li>
  <li>Check your chart validates with <code class="language-plaintext highlighter-rouge">helm lint</code> and <code class="language-plaintext highlighter-rouge">helm tempate</code></li>
  <li>Add unit tests using
<a href="https://github.com/helm-unittest/helm-unittest">helm unit test</a></li>
  <li>Add integration tests using
<a href="https://helm.sh/docs/topics/chart_tests/">helm’s native tests</a></li>
  <li>Generate documentation using
<a href="https://github.com/norwoodj/helm-docs">helm-docs</a></li>
  <li>Run linting, unit tests in CI before publishing</li>
  <li>Run integration tests immediately after your deployment.</li>
</ol>]]></content><author><name></name></author><category term="devops" /><category term="helm" /><category term="kubernetes" /><summary type="html"><![CDATA[Building robust helm charts]]></summary></entry><entry><title type="html">Parsing large XML files in node</title><link href="/javascript/node/lambda/2025/01/11/large-xml-files-in-node.html" rel="alternate" type="text/html" title="Parsing large XML files in node" /><published>2025-01-11T00:00:00+00:00</published><updated>2025-01-11T00:00:00+00:00</updated><id>/javascript/node/lambda/2025/01/11/large-xml-files-in-node</id><content type="html" xml:base="/javascript/node/lambda/2025/01/11/large-xml-files-in-node.html"><![CDATA[<h1 id="parsing-large-xml-files-in-node">Parsing large XML files in node</h1>

<p>I recently had to parse relatively large XML files (multi GB) to export product
data to a 3rd party API using node. Although this may sound like a trivial task
at first, I found a few interesting challenges along the way.</p>

<h2 id="library-choice">Library choice</h2>

<h3 id="xml-stream"><a href="https://www.npmjs.com/package/xml-stream">xml-stream</a></h3>

<p>I started with this library, although very old, it seemed to offer a good
balance of streamable event based parsing without too much difficulty extracting
the data. The library eventually wraps the c library
<a href="https://github.com/libexpat/libexpat">libexpat</a> so I expected good performance.</p>

<p>However I had issues with native code compatibility running it in AWS lambda.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/var/task/node_modules/node-expat/build/Release/node_expat.node: invalid ELF header
</code></pre></div></div>

<p>I came to the conclusion that some dependency of libexpat was not available in
the lambda runtime or that some pre-built binary wasn’t compatible.</p>

<h3 id="sax"><a href="https://www.npmjs.com/package/sax">SAX</a></h3>

<p>In the hope to resolve my native code compatibility issues, I turned to
<a href="https://www.npmjs.com/package/sax">sax</a>, a very popular pure javascript
implementation written by issacs (creator of npm). This did indeed resolve my
issues in AWS lambda, however with my first implementation, I found it a lot
more memory intensive than xml-stream. I started to have out of memory issues
especially when parsing the larger files (more on that later). It was also
somewhat slower than xml-stream.</p>

<h3 id="rust--neon">Rust + neon</h3>

<p>This was a little out there and more out of curiosity for performance
benchmarks. I had a little experience building a node js wrapper for a rust
crate using <a href="https://neon-rs.dev/">neon</a>. I wondered if I could wrap something
like <a href="https://github.com/tafia/quick-xml">quick xml</a> and get the best
performance. It turned out my rust skills were not quite up to the task.</p>

<h3 id="sax-wasm"><a href="https://www.npmjs.com/package/sax-wasm">sax-wasm</a></h3>

<p>After my adventures in rust, I came across
<a href="https://www.npmjs.com/package/sax-wasm">sax-wasm</a>. I wondered if I could
benefit from near native performance without the compatibility issues by using
something wasm based. My initial results were promising, however despite a very
responsive and talented maintainer, it became clear that the library was a
little young to be used in production code (lack of documentation, some bugs
with cdata, updated versions that broke). I did eventually get my code to work,
but decided that the more productive way forward was to stick with sax and
optimise my shoddy XML parsing code rather than search for a silver bullet
library.</p>

<h2 id="out-of-memory-issues">Out of memory issues</h2>

<p><img src="/assets/images/xml-parsing-in-node/import-all.png" alt="import all" width="800" style="display:block; margin: auto" /></p>

<p>Although I never stored the entire file in memory, my initial strategy was to
keep an array of all the product objects I’d parsed in memory until the full
file had been read. Once the file had been parsed, I then batched requests to
the api until all the products had been imported.</p>

<p>To fix the memory issues I changed strategy and came up with a threshold. I
would keep the parsed products in memory until that threshold was reached, then
send them off to the api, then fill up the next batch until all the products
were imported.</p>

<p><img src="/assets/images/xml-parsing-in-node/import-xml-in-chunks.png" alt="import in chunks" width="800" style="display:block; margin: auto" /></p>

<p>The sax library is an
<a href="https://nodejs.org/api/events.html#class-eventemitter">event emitter</a>, you pass
it an XML <a href="https://nodejs.org/api/stream.html#readable-streams">readable stream</a>
and it fires events when it sees various XML related entities. I decided to
implement my own event emitter that would send events as the products were
parsed.</p>

<p>Here’s a highly simplified version. with all the error handling and the vast
majority of the parsing logic removed.</p>

<p>to parse this XML:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;products&gt;</span>
  <span class="nt">&lt;product&gt;</span>
    <span class="nt">&lt;name&gt;</span>Fancy Trainers<span class="nt">&lt;/name&gt;</span>
  <span class="nt">&lt;/product&gt;</span>
<span class="nt">&lt;/products&gt;</span>
</code></pre></div></div>

<p>Use the following class</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">sax</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">sax</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">EventEmitter</span><span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">node:events</span><span class="dl">"</span><span class="p">;</span>

<span class="kd">class</span> <span class="nx">ProductParser</span> <span class="kd">extends</span> <span class="nx">EventEmitter</span> <span class="p">{</span>
  <span class="nx">parse</span><span class="p">(</span><span class="nx">readableStream</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">xml</span> <span class="o">=</span> <span class="nx">sax</span><span class="p">.</span><span class="nx">createStream</span><span class="p">(</span><span class="nx">strict</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
    <span class="kd">let</span> <span class="nx">product</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
    <span class="kd">let</span> <span class="nx">currentTag</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
    <span class="nx">xml</span>
      <span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">opentag</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">tag</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">tag</span><span class="p">.</span><span class="nx">name</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">product</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">product</span> <span class="o">=</span> <span class="p">{};</span>
        <span class="p">}</span>
        <span class="nx">currentTag</span> <span class="o">=</span> <span class="nx">tag</span><span class="p">.</span><span class="nx">name</span><span class="p">;</span>
      <span class="p">})</span>
      <span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">text</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">text</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">currentTag</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">name</span><span class="dl">"</span> <span class="o">&amp;&amp;</span> <span class="nx">product</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">product</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">text</span><span class="p">;</span>
        <span class="p">}</span>
      <span class="p">})</span>
      <span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">closetag</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">tag</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">tag</span><span class="p">.</span><span class="nx">name</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">product</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
          <span class="c1">// product is done, notify new product</span>
          <span class="k">this</span><span class="p">.</span><span class="nx">emit</span><span class="p">(</span><span class="dl">"</span><span class="s2">product</span><span class="dl">"</span><span class="p">,</span> <span class="nx">product</span><span class="p">);</span>
          <span class="nx">product</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
        <span class="p">}</span>
      <span class="p">})</span>
      <span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">end</span><span class="dl">"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="c1">// file has finished parsing</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">emit</span><span class="p">(</span><span class="dl">"</span><span class="s2">end</span><span class="dl">"</span><span class="p">);</span>
      <span class="p">});</span>
    <span class="nx">readable</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">xml</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Then orchestrate like this</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="kd">function</span> <span class="nx">importCatalogue</span><span class="p">(</span><span class="nx">xmlStream</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">THRESHOLD</span> <span class="o">=</span> <span class="mi">1000</span><span class="p">;</span>
    <span class="kd">const</span> <span class="nx">productParser</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ProductParser</span><span class="p">();</span>
    <span class="kd">let</span> <span class="nx">products</span> <span class="o">=</span> <span class="p">[];</span>

    <span class="nx">productParser</span>
      <span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">product</span><span class="dl">"</span><span class="p">,</span> <span class="k">async</span> <span class="p">(</span><span class="nx">product</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">products</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">product</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">products</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;=</span> <span class="nx">THRESHOLD</span><span class="p">)</span> <span class="p">{</span>
          <span class="kd">const</span> <span class="nx">batch</span> <span class="o">=</span> <span class="p">[...</span><span class="nx">products</span><span class="p">];</span>
          <span class="c1">// this is the important step. Reassigning 'products' prevents storing all products in memory.</span>
          <span class="nx">products</span> <span class="o">=</span> <span class="p">[];</span>
          <span class="k">await</span> <span class="nx">api</span><span class="p">.</span><span class="k">import</span><span class="p">(</span><span class="nx">batch</span><span class="p">);</span>
        <span class="p">}</span>
      <span class="p">})</span>
      <span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">end</span><span class="dl">"</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">products</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
          <span class="k">await</span> <span class="nx">api</span><span class="p">.</span><span class="k">import</span><span class="p">(</span><span class="nx">products</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="nx">resolve</span><span class="p">();</span>
      <span class="p">})</span>
      <span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">xmlStream</span><span class="p">);</span>
  <span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This strategy worked well, I observed the memory usage of my application
increase on start up then flatten out while it was going through all of the
products.</p>

<p>However, it led me to a subtle unpredictable bug…</p>

<h3 id="async-event-emitter-handlers">Async event emitter handlers</h3>

<p>Notice how the promise is resolved in the <code class="language-plaintext highlighter-rouge">'end'</code> event handler? Well the
problem is that if one of the <code class="language-plaintext highlighter-rouge">api.import()</code> calls is slow, we could resolve the
promise <strong>before</strong> the products have imported.</p>

<p>When running node locally, this isn’t a such a big problem because node waits
for any asynchronous activity before exiting. However AWS lambda simply waits
for your handler to complete (in this case to resolve).</p>

<p>This led to some batches being prematurely cut off without an obvious error or
explanation.</p>

<p>The fix I came up with, was to store the promises and await them in the end
handler.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">pendingHandlers</span> <span class="o">=</span> <span class="p">[];</span>
<span class="nx">productParser</span>
  <span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">product</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">product</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">pendingHandlers</span><span class="p">.</span><span class="nx">push</span><span class="p">((</span><span class="k">async</span> <span class="p">()</span> <span class="p">{</span>
      <span class="nx">products</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">product</span><span class="p">);</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">products</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;=</span> <span class="nx">THRESHOLD</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">batch</span> <span class="o">=</span> <span class="p">[...</span><span class="nx">products</span><span class="p">];</span>
        <span class="nx">products</span> <span class="o">=</span> <span class="p">[];</span>
        <span class="k">await</span> <span class="nx">api</span><span class="p">.</span><span class="k">import</span><span class="p">(</span><span class="nx">batch</span><span class="p">);</span>
      <span class="p">}</span>
    <span class="p">})())</span>
  <span class="p">})</span>
  <span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">end</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="c1">// Ensure all pending requests have completed</span>
    <span class="k">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">(</span><span class="nx">pendingHandlers</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">products</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">await</span> <span class="nx">api</span><span class="p">.</span><span class="k">import</span><span class="p">(</span><span class="nx">products</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="nx">resolve</span><span class="p">();</span>
  <span class="p">})</span>
  <span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">xmlStream</span><span class="p">)</span>
</code></pre></div></div>

<p>This worked pretty well, however I found another issue.</p>

<h3 id="requests-being-delayed-and-timing-out">Requests being delayed and timing out</h3>

<p>Although the requests were being sent out as parsing was going on, I found that
these were taking a very long time to complete.</p>

<p>On top of this, although I saw some improvement on memory usage, it was still
continuously increasing until the full file was parsed.</p>

<p>My explanation to this is that the XML parsing is flooding the node js event
loop with events so many events, that the callbacks used during the api request
end up very far behind in the queue.</p>

<p>One solution to this may have been to use
<a href="https://nodejs.org/api/worker_threads.html">worker threads</a> to send the api
requests however, I didn’t like the idea of going down the multi threaded route
in javascript.</p>

<p>Thankfully I came across
<a href="https://github.com/isaacs/sax-js/issues/105">this github</a> issue in sax.</p>

<p><img src="/assets/images/xml-parsing-in-node/pause-resume.png" alt="import in chunks" width="800" style="display:block; margin: auto" /></p>

<p>It turns out that readable streams have
<a href="https://nodejs.org/api/stream.html#readablepause"><code class="language-plaintext highlighter-rouge">pause()</code></a> and
<a href="https://nodejs.org/api/stream.html#readableresume"><code class="language-plaintext highlighter-rouge">resume()</code></a> functions which
allow you to stop piping data while you perform some operation, then continue
once you’re finished. So I changed my code to.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">productParser</span>
  <span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">product</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">product</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">pendingHandlers</span><span class="p">.</span><span class="nx">push</span><span class="p">((</span><span class="k">async</span> <span class="p">()</span> <span class="p">{</span>
      <span class="nx">products</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">product</span><span class="p">);</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">products</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;=</span> <span class="nx">THRESHOLD</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">batch</span> <span class="o">=</span> <span class="p">[...</span><span class="nx">products</span><span class="p">];</span>
        <span class="nx">products</span> <span class="o">=</span> <span class="p">[];</span>
        <span class="c1">// stop pipeing data while the api request is sent</span>
        <span class="nx">xmlStream</span><span class="p">.</span><span class="nx">pause</span><span class="p">();</span>
        <span class="k">await</span> <span class="nx">api</span><span class="p">.</span><span class="k">import</span><span class="p">(</span><span class="nx">batch</span><span class="p">);</span>
        <span class="c1">// continue pipeing data</span>
        <span class="nx">xmlStream</span><span class="p">.</span><span class="nx">resume</span><span class="p">();</span>
      <span class="p">}</span>
    <span class="p">})())</span>
  <span class="p">})</span>
</code></pre></div></div>

<h3 id="success">Success!</h3>

<p>I was now able to import large xml files without running out of memory, what
took 7GB of memory initially was reduced to 2GB without increasing the time
taken.</p>]]></content><author><name></name></author><category term="javascript" /><category term="node" /><category term="lambda" /><summary type="html"><![CDATA[Parsing large XML files in node]]></summary></entry><entry><title type="html">Mario generator</title><link href="/javascript/canvas/front-end/fun/2023/08/20/mario-generator.html" rel="alternate" type="text/html" title="Mario generator" /><published>2023-08-20T00:00:00+00:00</published><updated>2023-08-20T00:00:00+00:00</updated><id>/javascript/canvas/front-end/fun/2023/08/20/mario-generator</id><content type="html" xml:base="/javascript/canvas/front-end/fun/2023/08/20/mario-generator.html"><![CDATA[<h1 id="mario-generator">Mario generator</h1>

<p>I bought a second hand SNES mini recently and have been rediscovering the
fun of super mario world. I got inspired to make a bit of pixel art using the
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API">canvas api</a>. I wanted to generate different marios.</p>

<p>I started out with this image I found online.</p>

<p><img src="https://vignette.wikia.nocookie.net/fantendo/images/d/df/Super_mario_world_mario_sprite_by_sy24-d9ue2li.png/revision/latest?cb=20170106220014" alt="mario" width="100" /></p>

<p>The image contains 13 colors. I carefully translated the image to a 2D array of
characters, with each character mapping to a color. Don’t pay too much attention
to the actual characters, they’re pretty much random.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">template</span> <span class="o">=</span> <span class="p">[</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">p</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">p</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">y</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">p</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">p</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">p</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">p</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">P</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">g</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">y</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">P</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">p</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">P</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">P</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">p</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">P</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">P</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">P</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">P</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">P</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">s</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">s</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">s</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">s</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">s</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">s</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">s</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">s</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">s</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">s</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">s</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">s</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">s</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">s</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">s</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">P</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">P</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">p</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">p</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">P</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">P</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">p</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">p</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">p</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">P</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">P</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">p</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">p</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">P</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">P</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">p</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">p</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">P</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">P</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">t</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">a</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">a</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">a</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">t</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">t</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">t</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">t</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">a</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">a</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">a</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">a</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">a</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">t</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">t</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">t</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">t</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">a</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">a</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">t</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">t</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">t</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">t</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">t</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">t</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">t</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">n</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
  <span class="p">[</span><span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">m</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">y</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">b</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">y</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">],</span>
<span class="p">];</span>
</code></pre></div></div>

<p>To make each mario different I needed to generate 3 different shades of a random
color to use for his shirt. I used <code class="language-plaintext highlighter-rouge">rgb</code> notation for this and added a helper
function to generate 3 versions (light, medium and dark) of a color.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">rgb</span> <span class="o">=</span> <span class="p">(</span><span class="nx">r</span><span class="p">,</span> <span class="nx">g</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="s2">`rgb(</span><span class="p">${</span><span class="nx">r</span><span class="p">}</span><span class="s2">,</span><span class="p">${</span><span class="nx">g</span><span class="p">}</span><span class="s2">,</span><span class="p">${</span><span class="nx">b</span><span class="p">}</span><span class="s2">)`</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">getShades</span> <span class="o">=</span> <span class="p">(</span><span class="nx">r</span><span class="p">,</span> <span class="nx">g</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">{</span>
    <span class="na">light</span><span class="p">:</span> <span class="nx">rgb</span><span class="p">(</span><span class="nx">r</span><span class="p">,</span> <span class="nx">g</span><span class="p">,</span> <span class="nx">b</span><span class="p">),</span>
    <span class="na">medium</span><span class="p">:</span> <span class="nx">rgb</span><span class="p">(</span><span class="nx">r</span> <span class="o">*</span> <span class="mf">0.8</span><span class="p">,</span> <span class="nx">g</span> <span class="o">*</span> <span class="mf">0.8</span><span class="p">,</span> <span class="nx">b</span> <span class="o">*</span> <span class="mf">0.8</span><span class="p">),</span>
    <span class="na">dark</span><span class="p">:</span> <span class="nx">rgb</span><span class="p">(</span><span class="nx">r</span> <span class="o">*</span> <span class="mf">0.5</span><span class="p">,</span> <span class="nx">g</span> <span class="o">*</span> <span class="mf">0.5</span><span class="p">,</span> <span class="nx">b</span> <span class="o">*</span> <span class="mf">0.5</span><span class="p">),</span>
  <span class="p">};</span>
<span class="p">};</span>
</code></pre></div></div>
<p>I then mapped these to the corresponding letters in the template (<code class="language-plaintext highlighter-rouge">p</code>, <code class="language-plaintext highlighter-rouge">P</code> and <code class="language-plaintext highlighter-rouge">r</code>)
to give each mario some unique clothing.</p>

<p>You can see 10 generated marios below. If you refresh the page they will be different
every time. This was quite a fun simple way to play with the canvas api and I may revisit it in the future.</p>]]></content><author><name></name></author><category term="javascript" /><category term="canvas" /><category term="front-end" /><category term="fun" /><summary type="html"><![CDATA[Mario generator]]></summary></entry><entry><title type="html">Using localstack to test terraform</title><link href="/terraform/aws/devops/lambda/ioc/localstack/2023/04/29/using-localstack-to-test-terraform.html" rel="alternate" type="text/html" title="Using localstack to test terraform" /><published>2023-04-29T08:00:00+00:00</published><updated>2023-04-29T08:00:00+00:00</updated><id>/terraform/aws/devops/lambda/ioc/localstack/2023/04/29/using-localstack-to-test-terraform</id><content type="html" xml:base="/terraform/aws/devops/lambda/ioc/localstack/2023/04/29/using-localstack-to-test-terraform.html"><![CDATA[<p>I recently experimented with running an AWS application completely locally. Just like in local development, I wanted to be able to run my infrastructure as code on my machine and run tests against it before deploying it to a real world AWS environment. This would allow me to test things in a free and secure way.</p>

<p>In this post I will demonstrate this by deploying an aws lambda function with a function url, but the same technique can be used to deploy any infrastructure supported by localstack.</p>

<h2 id="prerequisites">Prerequisites</h2>

<ul>
  <li><a href="https://hub.docker.com/">docker</a></li>
  <li><a href="https://www.terraform.io/">terraform</a> (I’m using version 1.4.6)</li>
</ul>

<h2 id="1-setting-up-the-localstack-environment">1. Setting up the localstack environment</h2>

<p>Create the following <code class="language-plaintext highlighter-rouge">docker-compose.yaml</code> file</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">3.4'</span>
<span class="na">services</span><span class="pi">:</span>
  <span class="na">localstack</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">localstack/localstack:2.0.2</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s1">'</span><span class="s">4566:4566'</span>
    <span class="na">expose</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s1">'</span><span class="s">4566'</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">/var/run/docker.sock:/var/run/docker.sock</span>
</code></pre></div></div>

<p>Note that we are mounting the docker socket as a volume. Localstack pulls external docker images in order to run the lambda runtime so this is required.</p>

<p>Now run this to start the container.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker compose up <span class="nt">-d</span>
</code></pre></div></div>

<h3 id="creating-the-state-bucket">Creating the state bucket</h3>

<p>In a real world scenario, for your staging and production environments, you’re going to want to share the terraform state so that deploys can be made from multiple machines easily. Terraform does not support different backend types for different environments, but we can use s3 to hold our state.</p>

<p>the localstack image ships with a local version of the aws cli which we can use to create the bucket.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker compose <span class="nb">exec </span>localstack awslocal s3api create-bucket <span class="nt">--bucket</span> terraform-state
</code></pre></div></div>

<h2 id="2-setting-up-terraform">2. Setting up terraform</h2>

<p>Create a directory to store your terraform files and add a <code class="language-plaintext highlighter-rouge">main.tf</code></p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">variable</span> <span class="s2">"use_localstack"</span> <span class="p">{</span>
  <span class="nx">type</span>    <span class="p">=</span> <span class="nx">string</span>
  <span class="nx">default</span> <span class="p">=</span> <span class="kc">true</span>
<span class="p">}</span>

<span class="nx">terraform</span> <span class="p">{</span>
  <span class="nx">backend</span> <span class="s2">"s3"</span> <span class="p">{}</span>
<span class="p">}</span>

<span class="nx">locals</span> <span class="p">{</span>
  <span class="nx">aws_settings</span> <span class="p">=</span> <span class="err">(</span>
    <span class="nx">var</span><span class="err">.</span><span class="nx">use_localstack</span> <span class="err">?</span>
    <span class="p">{</span>
      <span class="nx">access_key</span> <span class="p">=</span> <span class="s2">"fake"</span>
      <span class="nx">secret_key</span> <span class="p">=</span> <span class="s2">"fake"</span>

      <span class="nx">skip_credentials_validation</span> <span class="p">=</span> <span class="kc">true</span>
      <span class="nx">skip_metadata_api_check</span>     <span class="p">=</span> <span class="kc">true</span>
      <span class="nx">skip_requesting_account_id</span>  <span class="p">=</span> <span class="kc">true</span>
      <span class="nx">s3_use_path_style</span>           <span class="p">=</span> <span class="kc">true</span>

      <span class="nx">override_endpoint</span> <span class="p">=</span> <span class="s2">"http://localhost:4566"</span>
      <span class="nx">profile</span>           <span class="p">=</span> <span class="kc">null</span>
    <span class="p">}</span> <span class="err">:</span>
    <span class="p">{</span>
      <span class="nx">access_key</span>                  <span class="p">=</span> <span class="kc">null</span>
      <span class="nx">secret_key</span>                  <span class="p">=</span> <span class="kc">null</span>
      <span class="nx">skip_credentials_validation</span> <span class="p">=</span> <span class="kc">null</span>
      <span class="nx">skip_metadata_api_check</span>     <span class="p">=</span> <span class="kc">null</span>
      <span class="nx">skip_requesting_account_id</span>  <span class="p">=</span> <span class="kc">null</span>
      <span class="nx">s3_use_path_style</span>           <span class="p">=</span> <span class="kc">null</span>

      <span class="nx">override_endpoint</span> <span class="p">=</span> <span class="kc">null</span>
    <span class="p">}</span>
  <span class="err">)</span>
  <span class="nx">archive_file</span> <span class="p">=</span> <span class="s2">"build.zip"</span>
<span class="p">}</span>


<span class="nx">provider</span> <span class="s2">"aws"</span> <span class="p">{</span>
  <span class="nx">access_key</span>                  <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">aws_settings</span><span class="err">.</span><span class="nx">access_key</span>
  <span class="nx">secret_key</span>                  <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">aws_settings</span><span class="err">.</span><span class="nx">secret_key</span>
  <span class="nx">region</span>                      <span class="p">=</span> <span class="s2">"eu-west-1"</span>
  <span class="nx">s3_use_path_style</span>           <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">aws_settings</span><span class="err">.</span><span class="nx">s3_use_path_style</span>
  <span class="nx">skip_credentials_validation</span> <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">aws_settings</span><span class="err">.</span><span class="nx">skip_credentials_validation</span>
  <span class="nx">skip_metadata_api_check</span>     <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">aws_settings</span><span class="err">.</span><span class="nx">skip_metadata_api_check</span>
  <span class="nx">skip_requesting_account_id</span>  <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">aws_settings</span><span class="err">.</span><span class="nx">skip_requesting_account_id</span>
  <span class="nx">dynamic</span> <span class="s2">"endpoints"</span> <span class="p">{</span>
    <span class="nx">for_each</span> <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">aws_settings</span><span class="err">.</span><span class="nx">override_endpoint</span><span class="p">[</span><span class="err">*</span><span class="p">]</span>
    <span class="nx">content</span> <span class="p">{</span>
      <span class="nx">lambda</span>   <span class="p">=</span> <span class="nx">endpoints</span><span class="err">.</span><span class="nx">value</span>
      <span class="nx">iam</span>      <span class="p">=</span> <span class="nx">endpoints</span><span class="err">.</span><span class="nx">value</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This set up allows us to easily switch between localstack and the real AWS using the <code class="language-plaintext highlighter-rouge">use_localstack</code> terraform variable. Note that by setting the various settings to <code class="language-plaintext highlighter-rouge">null</code> we are telling terraform to use the defaults. If you are using more aws services, you will need to override the endpoints like we’ve done for <code class="language-plaintext highlighter-rouge">lambda</code> and <code class="language-plaintext highlighter-rouge">iam</code> here.</p>

<p>Now that you have this set up, create a backend configuration for localstack in your terraform directory, create a file called <code class="language-plaintext highlighter-rouge">local.s3.tfbackend</code> pointing at the s3 bucket we made earlier.</p>

<p>The <code class="language-plaintext highlighter-rouge">force_path_style</code> property is required to work with s3 locally without additional hosts configuration.</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">region</span> <span class="err">=</span> <span class="s2">"eu-west-1"</span>
<span class="nx">bucket</span> <span class="err">=</span> <span class="s2">"terraform-state"</span>
<span class="nx">key</span> <span class="err">=</span> <span class="s2">"my-app.tfstate"</span>
<span class="nx">endpoint</span> <span class="err">=</span> <span class="s2">"http://localhost:4566"</span>
<span class="nx">sts_endpoint</span> <span class="err">=</span> <span class="s2">"http://localhost:4566"</span>
<span class="nx">force_path_style</span> <span class="err">=</span> <span class="kc">true</span>
</code></pre></div></div>

<p>You should now be able to initialise terraform</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>terraform <span class="nt">-chdir</span><span class="o">=</span>./terraform init <span class="nt">-backend-config</span><span class="o">=</span>./local.s3.tfbackend
</code></pre></div></div>

<h2 id="3-creating-your-infrastructure">3. Creating your infrastructure</h2>

<p>Now you can start defining infrastructure in your <code class="language-plaintext highlighter-rouge">main.tf</code> file.</p>

<p>In this example I’m creating a lambda function with a simple function url.</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">data</span> <span class="s2">"aws_iam_policy_document"</span> <span class="s2">"assume_role"</span> <span class="p">{</span>
  <span class="nx">statement</span> <span class="p">{</span>
    <span class="nx">effect</span> <span class="p">=</span> <span class="s2">"Allow"</span>

    <span class="nx">principals</span> <span class="p">{</span>
      <span class="nx">type</span>        <span class="p">=</span> <span class="s2">"Service"</span>
      <span class="nx">identifiers</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"lambda.amazonaws.com"</span><span class="p">]</span>
    <span class="p">}</span>

    <span class="nx">actions</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"sts:AssumeRole"</span><span class="p">]</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_iam_role"</span> <span class="s2">"iam_for_lambda"</span> <span class="p">{</span>
  <span class="nx">name</span>               <span class="p">=</span> <span class="s2">"redirects_lambda_role"</span>
  <span class="nx">assume_role_policy</span> <span class="p">=</span> <span class="nx">data</span><span class="err">.</span><span class="nx">aws_iam_policy_document</span><span class="err">.</span><span class="nx">assume_role</span><span class="err">.</span><span class="nx">json</span>
<span class="p">}</span>

<span class="nx">data</span> <span class="s2">"archive_file"</span> <span class="s2">"lambda"</span> <span class="p">{</span>
  <span class="nx">type</span>        <span class="p">=</span> <span class="s2">"zip"</span>
  <span class="nx">source_file</span> <span class="p">=</span> <span class="s2">"../index.js"</span>
  <span class="nx">output_path</span> <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">archive_file</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_lambda_function"</span> <span class="s2">"redirect_lambda"</span> <span class="p">{</span>
  <span class="nx">filename</span>      <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">archive_file</span>
  <span class="nx">function_name</span> <span class="p">=</span> <span class="s2">"my-function"</span>
  <span class="nx">role</span>          <span class="p">=</span> <span class="nx">aws_iam_role</span><span class="err">.</span><span class="nx">iam_for_lambda</span><span class="err">.</span><span class="nx">arn</span>
  <span class="nx">handler</span>       <span class="p">=</span> <span class="s2">"index.main"</span>

  <span class="nx">source_code_hash</span> <span class="p">=</span> <span class="nx">data</span><span class="err">.</span><span class="nx">archive_file</span><span class="err">.</span><span class="nx">lambda</span><span class="err">.</span><span class="nx">output_base64sha256</span>

  <span class="nx">runtime</span> <span class="p">=</span> <span class="s2">"nodejs18.x"</span>

  <span class="nx">environment</span> <span class="p">{</span>
    <span class="nx">variables</span> <span class="p">=</span> <span class="p">{</span>
      <span class="nx">NODE_OPTIONS</span> <span class="p">=</span> <span class="s2">"--enable-source-maps"</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_lambda_function_url"</span> <span class="s2">"lambda_url"</span> <span class="p">{</span>
  <span class="nx">function_name</span>      <span class="p">=</span> <span class="nx">aws_lambda_function</span><span class="err">.</span><span class="nx">redirect_lambda</span><span class="err">.</span><span class="nx">function_name</span>
  <span class="nx">authorization_type</span> <span class="p">=</span> <span class="s2">"NONE"</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This will deploy the infrastructure for a lambda function from a file called <code class="language-plaintext highlighter-rouge">index.js</code> in the root.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">exports</span><span class="p">.</span><span class="nx">main</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">{</span>
    <span class="na">statusCode</span><span class="p">:</span> <span class="mi">200</span><span class="p">,</span>
    <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
      <span class="dl">'</span><span class="s1">Content-Type</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span>
    <span class="p">},</span>
    <span class="na">body</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Hello World!</span><span class="dl">'</span>
    <span class="p">}</span>
  <span class="p">};</span>
<span class="p">};</span>
</code></pre></div></div>

<p>Now run terraform plan and apply your changes</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>terraform <span class="nt">-chdir</span><span class="o">=</span>./terraform plan <span class="nt">-out</span> terraform.plan <span class="o">&amp;&amp;</span> <span class="se">\</span>
terraform <span class="nt">-chdir</span><span class="o">=</span>./terraform apply terraform.plan
</code></pre></div></div>

<p>You should now have your lambda function deployed.</p>

<h2 id="4-invoking-your-function">4. Invoking your function</h2>

<p>To get the url for your lambda function, inspect the state</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>terraform <span class="nt">-chdir</span><span class="o">=</span>./terraform state show aws_lambda_function_url.lambda_url
</code></pre></div></div>

<p>This will return you information about the resource including the <code class="language-plaintext highlighter-rouge">function_url</code></p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># aws_lambda_function_url.lambda_url:</span>
<span class="nx">resource</span> <span class="s2">"aws_lambda_function_url"</span> <span class="s2">"lambda_url"</span> <span class="p">{</span>
    <span class="nx">authorization_type</span> <span class="p">=</span> <span class="s2">"NONE"</span>
    <span class="nx">function_arn</span>       <span class="p">=</span> <span class="s2">"arn:aws:lambda:eu-west-1:000000000000:function:my-function"</span>
    <span class="nx">function_name</span>      <span class="p">=</span> <span class="s2">"my-function"</span>
    <span class="nx">function_url</span>       <span class="p">=</span> <span class="s2">"http://e6iu1sghet0ujaq6757ukxsjo6h50t90.lambda-url.eu-west-1.localhost.localstack.cloud:4566/"</span>
    <span class="nx">id</span>                 <span class="p">=</span> <span class="s2">"my-function"</span>
    <span class="nx">url_id</span>             <span class="p">=</span> <span class="s2">"e6iu1sghet0ujaq6757ukxsjo6h50t90"</span>
<span class="p">}</span>
</code></pre></div></div>

<p>you can then call the function</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-v</span> http://e6iu1sghet0ujaq6757ukxsjo6h50t90.lambda-url.eu-west-1.localhost.localstack.cloud:4566/ 
</code></pre></div></div>

<h3 id="️-status-code-issu️e-️">⚠️ Status code issu️e ⚠️</h3>

<p>As good as localstack is, it’s not a 100% true implementation of AWS. At time of writing there is <a href="https://github.com/localstack/localstack/issues/8213">an issue</a> where function urls will always return a 200 http status even when the lambda code explicitly sets a different status code. In a real AWS environment, the proper status code is returned.</p>

<h2 id="4-going-live-in-aws">4. Going live in AWS</h2>

<p>Now that you have your infra and production code tested locally you’re ready to go to a real AWS environment. In your <code class="language-plaintext highlighter-rouge">terraform</code> directory create a new backend configuration called <code class="language-plaintext highlighter-rouge">production.s3.tfbackend</code></p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">region</span> <span class="err">=</span> <span class="s2">"eu-west-1"</span>
<span class="nx">bucket</span> <span class="err">=</span> <span class="s2">"terraform-state-production"</span>
<span class="nx">key</span> <span class="err">=</span> <span class="s2">"my-function.tfstate"</span>
</code></pre></div></div>

<p><em>Ensure you’ve created the state bucket in aws first</em></p>

<p>Reinitialise terraform for this backend</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>terraform <span class="nt">-chdir</span><span class="o">=</span>./terraform init <span class="se">\</span>
  <span class="nt">-backend-config</span><span class="o">=</span>./production.s3.tfbackend <span class="se">\</span>
  <span class="nt">-reconfigure</span>
</code></pre></div></div>

<p>Deploy your infrastructure setting the <code class="language-plaintext highlighter-rouge">use_localstack</code> variable to <code class="language-plaintext highlighter-rouge">false</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>terraform -chdir=./terraform plan \
  -var 'use_localstack=false' \
  -out terraform.plan &amp;&amp; \
terraform -chdir=./terraform apply \
  -var 'use_localstack=false' terraform.plan
</code></pre></div></div>

<p>You should now be able to repeat exactly the same steps as you made locally to run your function.</p>

<h2 id="references">References</h2>

<ul>
  <li><a href="https://stackoverflow.com/a/69731567/752756">Terraform override local provider for use with localstack</a></li>
  <li><a href="https://docs.localstack.cloud/user-guide/integrations/terraform/">Localstack terraform documentation</a></li>
</ul>]]></content><author><name></name></author><category term="terraform" /><category term="aws" /><category term="devops" /><category term="lambda" /><category term="ioc" /><category term="localstack" /><summary type="html"><![CDATA[I recently experimented with running an AWS application completely locally. Just like in local development, I wanted to be able to run my infrastructure as code on my machine and run tests against it before deploying it to a real world AWS environment. This would allow me to test things in a free and secure way.]]></summary></entry><entry><title type="html">🎾 Implementing fetch in node 🐕</title><link href="/javascript/node/http/2023/04/15/implementing-fetch-in-node.html" rel="alternate" type="text/html" title="🎾 Implementing fetch in node 🐕" /><published>2023-04-15T14:00:00+00:00</published><updated>2023-04-15T14:00:00+00:00</updated><id>/javascript/node/http/2023/04/15/implementing-fetch-in-node</id><content type="html" xml:base="/javascript/node/http/2023/04/15/implementing-fetch-in-node.html"><![CDATA[<h1 id="-implementing-fetch-in-node-">🎾 Implementing fetch in node 🐕</h1>

<p>node 18 provides the handy <code class="language-plaintext highlighter-rouge">fetch()</code> function which we can use to make HTTP requests to a remote server. But what exactly does <code class="language-plaintext highlighter-rouge">fetch</code> do? This is not a question we often ask ourselves but it’s a good opportunity to step down a level and appreciate the networking required to give us such a function.</p>

<p>When talking about networking, you’ll often here people mention the <a href="https://en.wikipedia.org/wiki/OSI_model">Open Systems Interconnection (OSI) model</a>. This is a way of breaking down networking technologies into <em>layers</em></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>7. Application
6. Presentation
5. Session
4. Transport
3. Network
2. Data link
1. Physical 
</code></pre></div></div>

<p>HTTP is a layer 7 or <em>application protocol</em>. It provides a standard way for clients and servers to communicate over a layer 4 <em>transport protocol</em>. Most commonly, that transport protocol is TCP although more recent evolutions of HTTP explore using other transport protocols such as <a href="https://en.wikipedia.org/wiki/QUIC">QUIC</a> to improve some of the limitations of TCP. The scope of this article is to implement a fetch function for HTTP 1.1 (not HTTPS) connections over TCP IP only.</p>

<p>With that little bit of theory out of the way, lets get going.</p>

<h2 id="the-task">The task</h2>

<p>We’ll be looking at implementing this piece of code.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">http://example.com</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nx">text</span><span class="p">());</span>
</code></pre></div></div>

<h2 id="url-parsing">URL parsing</h2>

<p>The <a href="https://nodejs.org/dist/latest-v18.x/docs/api/url.html#class-url"><code class="language-plaintext highlighter-rouge">url</code></a> module can be used to parse the url. In order to write our fetch function we need to split this string:</p>

<p><strong>http://example.com/?foo=bar#hash</strong></p>

<p>into it’s parts</p>

<table>
  <thead>
    <tr>
      <th>Protocol</th>
      <th>hostname</th>
      <th>port</th>
      <th>pathname</th>
      <th>search</th>
      <th>hash</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>http:</td>
      <td>localhost</td>
      <td>80 (the default port for http)</td>
      <td>/</td>
      <td>?foo=bar</td>
      <td>#hash</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="url-parsing-1">URL parsing</h2>

<p>This is exactly what the <code class="language-plaintext highlighter-rouge">URL</code> constructor does.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">URL</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">url</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URL</span><span class="p">(</span><span class="dl">'</span><span class="s1">http://example.com/?foo=bar#hash</span><span class="dl">'</span><span class="p">);</span>

<span class="nx">URL</span> <span class="p">{</span>
  <span class="nl">href</span><span class="p">:</span> <span class="dl">'</span><span class="s1">http://example.com/?foo=bar#hash</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">origin</span><span class="p">:</span> <span class="dl">'</span><span class="s1">http://example.com</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">protocol</span><span class="p">:</span> <span class="dl">'</span><span class="s1">http:</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">username</span><span class="p">:</span> <span class="dl">''</span><span class="p">,</span>
  <span class="nx">password</span><span class="p">:</span> <span class="dl">''</span><span class="p">,</span>
  <span class="nx">host</span><span class="p">:</span> <span class="dl">'</span><span class="s1">example.com</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">hostname</span><span class="p">:</span> <span class="dl">'</span><span class="s1">example.com</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">port</span><span class="p">:</span> <span class="dl">''</span><span class="p">,</span>
  <span class="nx">pathname</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">search</span><span class="p">:</span> <span class="dl">'</span><span class="s1">?foo=bar</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">searchParams</span><span class="p">:</span> <span class="nx">URLSearchParams</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">foo</span><span class="dl">'</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span> <span class="p">},</span>
  <span class="nx">hash</span><span class="p">:</span> <span class="dl">'</span><span class="s1">#hash</span><span class="dl">'</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Note that this gives us everything except the port which we need to default to <code class="language-plaintext highlighter-rouge">80</code>.</p>

<h2 id="dns-lookup">DNS lookup</h2>

<p>Now that we’ve split the url, in order to make an http request over TCP IP, we need to have the IP address of the server we’re trying to connect to. We don’t typically remember website’s based on IP address this is where DNS and the <a href="https://nodejs.org/dist/latest-v18.x/docs/api/dns.html#dnspromiseslookuphostname-options">dns module</a> comes in handy. It allows us to convert a host name to an ip address.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">promises</span> <span class="k">as</span> <span class="nx">dns</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">node:dns</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">dnsRecord</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">dns</span><span class="p">.</span><span class="nx">lookup</span><span class="p">(</span><span class="dl">'</span><span class="s1">example.com</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">//dnsRecord.address -&gt; 93.184.216.34</span>
<span class="c1">//dnsRecord.family -&gt; 4</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">address</code> is the ip address we looked up and <code class="language-plaintext highlighter-rouge">family</code> tells us it’s an IPV4 address as opposed to IPV6</p>

<h2 id="connecting-over-tcp-ip">Connecting over TCP IP</h2>

<p>The <a href="https://nodejs.org/dist/latest-v18.x/docs/api/net.html#netcreateconnection"><code class="language-plaintext highlighter-rouge">net</code> module</a> allows us to make TCP connections. It needs the ip and the port. The <code class="language-plaintext highlighter-rouge">createConnection</code> function returns a <a href="https://nodejs.org/dist/latest-v18.x/docs/api/stream.html#duplex-and-transform-streams">Duplex stream</a>.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">createConnection</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">net</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">exampleDotComIp</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">93.184.216.34</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">connection</span> <span class="o">=</span> <span class="nx">createConnection</span><span class="p">(</span>
  <span class="p">{</span><span class="na">port</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span> <span class="na">host</span><span class="p">:</span> <span class="nx">exampleDotComIp</span><span class="p">},</span>
  <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">connected!</span><span class="dl">'</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">);</span>
</code></pre></div></div>

<p>If everything went well, the callback function was called to log we have created a connection to the remote server.</p>

<h2 id="building-our-http-request">Building our HTTP request</h2>

<p>Now that we have an open TCP connection to <code class="language-plaintext highlighter-rouge">example.com</code>, we need to send it some data to initiate an HTTP request.</p>

<p>HTTP requests follow the follow the following format</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">method</code> - a valid <a href="https://en.wikipedia.org/wiki/HTTP#Request_methods">HTTP request method</a> (eg <code class="language-plaintext highlighter-rouge">GET</code> or <code class="language-plaintext highlighter-rouge">POST</code>)</li>
  <li><code class="language-plaintext highlighter-rouge">path</code> - the pathname from our parsed URL object.</li>
  <li><code class="language-plaintext highlighter-rouge">version</code> - the version of HTTP we are using (1.1 in our case)</li>
  <li><code class="language-plaintext highlighter-rouge">headers</code> - key value pairs of the request headers specified separated by a <code class="language-plaintext highlighter-rouge">:</code></li>
  <li><code class="language-plaintext highlighter-rouge">request body</code> GET requests don’t typically have a body but if we were sending a <code class="language-plaintext highlighter-rouge">POST</code> request, this would follow the headers.</li>
</ul>

<p>At a minimum the server requires us to specify the host header as HTTP allows multiple hosts to reside on the same IP address.</p>

<p>Here’s the request we’re aiming for.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GET / HTTP/1.1
host: example.com

</code></pre></div></div>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">createConnection</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">net</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">exampleDotComIp</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">93.184.216.34</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">connection</span> <span class="o">=</span> <span class="nx">createConnection</span><span class="p">(</span>
  <span class="p">{</span><span class="na">port</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span> <span class="na">host</span><span class="p">:</span> <span class="nx">exampleDotComIp</span><span class="p">},</span>
  <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">request</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">GET / HTTP/1.1</span><span class="se">\n</span><span class="dl">'</span> <span class="o">+</span>
      <span class="dl">'</span><span class="s1">host: example.com</span><span class="se">\n\n</span><span class="dl">'</span><span class="p">;</span>
    <span class="nx">connection</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="nx">request</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">);</span>
</code></pre></div></div>

<p>We just made our HTTP request. However this isn’t much use to us because we’re not handling the server’s response in any way.</p>

<h2 id="handling-the-http-response">Handling the HTTP response</h2>

<p>Just like HTTP requests, responses from the server also get sent in a standard format and includes the following information.</p>

<ul>
  <li>The HTTP <code class="language-plaintext highlighter-rouge">version</code> that the server is responding with</li>
  <li>The HTTP <a href="https://httpwg.org/specs/rfc9110.html#status.codes">status code</a> of the response to indicate how the request was processed</li>
  <li>The response headers in the same format as the request headers above</li>
  <li>The response body</li>
</ul>

<p>A successful response will look something like this</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HTTP/1.1 200 OK
response_header_key: response_header_value

&lt;DOCTYPE html&gt;
&lt;html&gt;.....
</code></pre></div></div>

<p>Let’s take a second to go back the code we’re trying to implement.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">http://example.com</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nx">text</span><span class="p">());</span>
</code></pre></div></div>

<p>Now the http response is potentially very large, maybe even <em>infinitely</em> large. In order for <code class="language-plaintext highlighter-rouge">fetch</code> to provide an api that can be useful in all scenarios, it doesn’t read the entire response before resolving. Instead, it reads the status line and the headers then returns control back to the client. This allows the client to inspect the status and headers before deciding what to do with the response body.</p>

<p>To start with let’s focus on the first line and resolve the <code class="language-plaintext highlighter-rouge">fetch()</code> promise.</p>

<h3 id="reading-the-response-headers">Reading the response headers</h3>

<p>Our client code is not overly concerned with the status or headers. Lets start by just resolving once the headers have been read. Now <a href="https://www.rfc-editor.org/rfc/rfc2616#section-6">the HTTP spec</a> tells us that the headers end once we receive a carriage return and line feed (<code class="language-plaintext highlighter-rouge">CRLF</code>) sequence. This is represented by the <code class="language-plaintext highlighter-rouge">bodyStartMarker</code> variable.
When the TCP stream emits a new chunk of data, we look at what has already been received and check if we have a <code class="language-plaintext highlighter-rouge">CRLF</code> sequence. If we do, we pause the stream and callback to the consumer with a response object containing the <code class="language-plaintext highlighter-rouge">text</code> function. This in turn resolves the <code class="language-plaintext highlighter-rouge">fetch</code> promise leaving the decision of what to do next up to the consumer.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">readResponse</span> <span class="o">=</span> <span class="p">(</span><span class="nx">connection</span><span class="p">,</span> <span class="nx">onHeaders</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">bodyStartMarker</span> <span class="o">=</span> <span class="dl">'</span><span class="se">\n\r</span><span class="dl">'</span><span class="p">;</span>
  <span class="kd">let</span> <span class="nx">rawHeaders</span> <span class="o">=</span> <span class="dl">''</span><span class="p">;</span>

  <span class="nx">connection</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">data</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">char</span> <span class="k">of</span> <span class="nx">data</span><span class="p">.</span><span class="nx">toString</span><span class="p">(</span><span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">previousChar</span> <span class="o">=</span> <span class="nx">rawHeaders</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
      <span class="k">if</span> <span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">previousChar</span><span class="p">}${</span><span class="nx">char</span><span class="p">}</span><span class="s2">`</span> <span class="o">===</span> <span class="nx">bodyStartMarker</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">connection</span><span class="p">.</span><span class="nx">pause</span><span class="p">();</span>
        <span class="nx">onHeaders</span><span class="p">({</span>
          <span class="k">async</span> <span class="nx">text</span><span class="p">()</span> <span class="p">{}</span>
        <span class="p">})</span>
        <span class="k">continue</span><span class="p">;</span>
      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nx">rawHeaders</span> <span class="o">+=</span> <span class="nx">char</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">});</span>
<span class="p">};</span>

<span class="kd">const</span> <span class="nx">fetch</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="c1">// parseUrl();</span>
  <span class="c1">// getIpFromDNSLookup();</span>
  <span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">connection</span> <span class="o">=</span> <span class="nx">createConnection</span><span class="p">(</span>
      <span class="p">{</span><span class="na">port</span><span class="p">:</span> <span class="nx">port</span><span class="p">,</span> <span class="na">host</span><span class="p">:</span> <span class="nx">ip</span><span class="p">},</span>
      <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="c1">// sendRequest();</span>
        <span class="nx">readResponse</span><span class="p">(</span><span class="nx">connection</span> <span class="nx">resolve</span><span class="p">);</span>
      <span class="p">}</span>
    <span class="p">);</span>
  <span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>

<p>You might think that we can simply listen for the connection’s <code class="language-plaintext highlighter-rouge">end</code> event to tell us when the response is finished however this is not the case. This is because the TCP connection remains open after the response is fully sent. This is actually a great thing because setting up a TCP connection is expensive. Keeping it open allows clients to reuse the same TCP connection for multiple HTTP requests.</p>

<p>There is therefore one response header that we do care about. The <code class="language-plaintext highlighter-rouge">content-length</code> header tells us how many bytes the body contains. This is how we know when the full body has been sent and we can resolve the <code class="language-plaintext highlighter-rouge">text()</code> promise Later on.</p>

<p>Once we’ve received the headers we can try to parse the header before calling back so we have it for later. I’ve done this here using a regexp with a named group.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">parseContentLength</span> <span class="o">=</span> <span class="p">(</span><span class="nx">rawHeaders</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">pattern</span> <span class="o">=</span> <span class="sr">/content-length: </span><span class="se">(?&lt;</span><span class="sr">contentLength&gt;</span><span class="se">\d</span><span class="sr">+</span><span class="se">)</span><span class="sr">/i</span><span class="p">;</span>
  <span class="k">return</span> <span class="nb">Number</span><span class="p">(</span>
    <span class="nx">rawHeaders</span><span class="p">.</span><span class="nx">match</span><span class="p">(</span><span class="nx">pattern</span><span class="p">).</span><span class="nx">groups</span><span class="p">.</span><span class="nx">contentLength</span>
  <span class="p">);</span>
<span class="p">};</span>

<span class="kd">const</span> <span class="nx">readResponse</span> <span class="o">=</span> <span class="p">(</span><span class="nx">connection</span><span class="p">,</span> <span class="nx">onHeaders</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">bodyStartMarker</span> <span class="o">=</span> <span class="dl">'</span><span class="se">\n\r</span><span class="dl">'</span><span class="p">;</span>
  <span class="kd">let</span> <span class="nx">contentLength</span><span class="p">;</span>
  <span class="kd">let</span> <span class="nx">rawHeaders</span> <span class="o">=</span> <span class="dl">''</span><span class="p">;</span>

  <span class="nx">connection</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">data</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">char</span> <span class="k">of</span> <span class="nx">data</span><span class="p">.</span><span class="nx">toString</span><span class="p">(</span><span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">previousChar</span> <span class="o">=</span> <span class="nx">rawHeaders</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
      <span class="nx">contentLength</span> <span class="o">=</span> <span class="nx">parseContentLength</span><span class="p">(</span><span class="nx">rawHeaders</span><span class="p">);</span>

      <span class="k">if</span> <span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">previousChar</span><span class="p">}${</span><span class="nx">char</span><span class="p">}</span><span class="s2">`</span> <span class="o">===</span> <span class="nx">bodyStartMarker</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">connection</span><span class="p">.</span><span class="nx">pause</span><span class="p">();</span>
        <span class="nx">onHeaders</span><span class="p">({</span>
          <span class="k">async</span> <span class="nx">text</span><span class="p">()</span> <span class="p">{}</span>
        <span class="p">})</span>
        <span class="k">continue</span><span class="p">;</span>
      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nx">rawHeaders</span> <span class="o">+=</span> <span class="nx">char</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">});</span>
<span class="p">};</span>
</code></pre></div></div>

<h3 id="reading-the-response-body">Reading the response body</h3>

<p>Now let’s focus on the next line of our client code.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nx">text</span><span class="p">());</span>
</code></pre></div></div>

<p>In our current implementation this will print <code class="language-plaintext highlighter-rouge">undefined</code>. What we need to do is resume reading the rest of the response data when the <code class="language-plaintext highlighter-rouge">text()</code> method is called and resolve it when we’ve read the number of bytes specified in our <code class="language-plaintext highlighter-rouge">contentLength</code>.</p>

<p>Our <code class="language-plaintext highlighter-rouge">data</code> event handler needs to do something different once the response headers have been sent to start writing the body so let’s introduce a <code class="language-plaintext highlighter-rouge">headersRead</code> flag. let’s also introduce a new <code class="language-plaintext highlighter-rouge">body</code> variable to store the body.</p>

<p>Once the headers have been read let’s make the <code class="language-plaintext highlighter-rouge">text()</code> function resume reading the rest of the data.</p>

<p>Once we’ve read the number of bytes specified in our <code class="language-plaintext highlighter-rouge">contentLength</code> we resolve the <code class="language-plaintext highlighter-rouge">text()</code> promise.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">readResponse</span> <span class="o">=</span> <span class="p">(</span><span class="nx">connection</span><span class="p">,</span> <span class="nx">onHeaders</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">bodyStartMarker</span> <span class="o">=</span> <span class="dl">'</span><span class="se">\n\r</span><span class="dl">'</span><span class="p">;</span>
  <span class="kd">let</span> <span class="nx">contentLength</span><span class="p">;</span>
  <span class="kd">let</span> <span class="nx">onBody</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{};</span>
  <span class="kd">let</span> <span class="nx">rawHeaders</span> <span class="o">=</span> <span class="dl">''</span><span class="p">;</span>
  <span class="kd">let</span> <span class="nx">body</span> <span class="o">=</span> <span class="dl">''</span><span class="p">;</span>
  <span class="kd">let</span> <span class="nx">headersRead</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>

  <span class="nx">connection</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">data</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">char</span> <span class="k">of</span> <span class="nx">data</span><span class="p">.</span><span class="nx">toString</span><span class="p">(</span><span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">headersRead</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">previousChar</span> <span class="o">=</span> <span class="nx">rawHeaders</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">previousChar</span><span class="p">}${</span><span class="nx">char</span><span class="p">}</span><span class="s2">`</span> <span class="o">===</span> <span class="nx">bodyStartMarker</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">contentLength</span> <span class="o">=</span> <span class="nx">parseContentLength</span><span class="p">(</span><span class="nx">rawHeaders</span><span class="p">);</span>
          <span class="nx">headersRead</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
          <span class="nx">connection</span><span class="p">.</span><span class="nx">pause</span><span class="p">();</span>
          <span class="nx">onHeaders</span><span class="p">({</span>
            <span class="k">async</span> <span class="nx">text</span><span class="p">()</span> <span class="p">{</span>
              <span class="nx">connection</span><span class="p">.</span><span class="nx">resume</span><span class="p">();</span>
              <span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
                <span class="nx">onBody</span> <span class="o">=</span> <span class="nx">resolve</span><span class="p">;</span>
              <span class="p">})</span>
            <span class="p">}</span>
          <span class="p">})</span>
          <span class="k">continue</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="nx">rawHeaders</span> <span class="o">+=</span> <span class="nx">char</span><span class="p">;</span>
        <span class="k">continue</span><span class="p">;</span>
      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nx">body</span> <span class="o">+=</span> <span class="nx">char</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">body</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="nx">contentLength</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">connection</span><span class="p">.</span><span class="nx">end</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">onBody</span><span class="p">(</span><span class="nx">body</span><span class="p">));</span>
        <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">});</span>
<span class="p">};</span>
</code></pre></div></div>

<h2 id="️-warning-happy-path-only">⚠️ Warning happy path only</h2>

<p>We now have a working implementation of our fetch function. It should work for any HTTP 1.1 server over TCP returning a text based body. However a lot could go wrong here that we would want to handle in a real worlds implementation.</p>

<ul>
  <li>The URL being invalid</li>
  <li>Using an unsupported protocol</li>
  <li>Failed DNS lookups</li>
  <li>The TLS connection failing or being unexpectedly dropped</li>
  <li>No response body (eg in a 204 no content response)</li>
  <li>All types of bad response data (missing/invalid <code class="language-plaintext highlighter-rouge">content-length</code>, headers never properly closed, body ending before we expect, etc)</li>
</ul>

<p>We also should avoid leaking TCP connections and close the connection once it’s no longer needed.</p>

<p>I won’t go into the implementation of these in this post but it’s important to note them.</p>

<p>As a closing note, here’s our final implementation.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">import</span> <span class="p">{</span><span class="nx">URL</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">url</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">promises</span> <span class="k">as</span> <span class="nx">dns</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">node:dns</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">createConnection</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">net</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">parseContentLength</span> <span class="o">=</span> <span class="p">(</span><span class="nx">rawHeaders</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">pattern</span> <span class="o">=</span> <span class="sr">/content-length: </span><span class="se">(?&lt;</span><span class="sr">contentLength&gt;</span><span class="se">\d</span><span class="sr">+</span><span class="se">)</span><span class="sr">/i</span><span class="p">;</span>
  <span class="k">return</span> <span class="nb">Number</span><span class="p">(</span>
    <span class="nx">rawHeaders</span><span class="p">.</span><span class="nx">match</span><span class="p">(</span><span class="nx">pattern</span><span class="p">).</span><span class="nx">groups</span><span class="p">.</span><span class="nx">contentLength</span>
  <span class="p">);</span>
<span class="p">};</span>

<span class="kd">const</span> <span class="nx">readResponse</span> <span class="o">=</span> <span class="p">(</span><span class="nx">connection</span><span class="p">,</span> <span class="nx">onHeaders</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">bodyStartMarker</span> <span class="o">=</span> <span class="dl">'</span><span class="se">\n\r</span><span class="dl">'</span><span class="p">;</span>
  <span class="kd">let</span> <span class="nx">contentLength</span><span class="p">;</span>
  <span class="kd">let</span> <span class="nx">onBody</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{};</span>
  <span class="kd">let</span> <span class="nx">rawHeaders</span> <span class="o">=</span> <span class="dl">''</span><span class="p">;</span>
  <span class="kd">let</span> <span class="nx">body</span> <span class="o">=</span> <span class="dl">''</span><span class="p">;</span>
  <span class="kd">let</span> <span class="nx">headersRead</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>

  <span class="nx">connection</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">data</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">char</span> <span class="k">of</span> <span class="nx">data</span><span class="p">.</span><span class="nx">toString</span><span class="p">(</span><span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">headersRead</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">previousChar</span> <span class="o">=</span> <span class="nx">rawHeaders</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">previousChar</span><span class="p">}${</span><span class="nx">char</span><span class="p">}</span><span class="s2">`</span> <span class="o">===</span> <span class="nx">bodyStartMarker</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">contentLength</span> <span class="o">=</span> <span class="nx">parseContentLength</span><span class="p">(</span><span class="nx">rawHeaders</span><span class="p">);</span>
          <span class="nx">headersRead</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
          <span class="nx">connection</span><span class="p">.</span><span class="nx">pause</span><span class="p">();</span>
          <span class="nx">onHeaders</span><span class="p">({</span>
            <span class="k">async</span> <span class="nx">text</span><span class="p">()</span> <span class="p">{</span>
              <span class="nx">connection</span><span class="p">.</span><span class="nx">resume</span><span class="p">();</span>
              <span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
                <span class="nx">onBody</span> <span class="o">=</span> <span class="nx">resolve</span><span class="p">;</span>
              <span class="p">})</span>
            <span class="p">}</span>
          <span class="p">})</span>
          <span class="k">continue</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="nx">rawHeaders</span> <span class="o">+=</span> <span class="nx">char</span><span class="p">;</span>
        <span class="k">continue</span><span class="p">;</span>
      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nx">body</span> <span class="o">+=</span> <span class="nx">char</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">body</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="nx">contentLength</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">connection</span><span class="p">.</span><span class="nx">end</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">onBody</span><span class="p">(</span><span class="nx">body</span><span class="p">));</span>
        <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">});</span>
<span class="p">};</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">fetch</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">urlString</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URL</span><span class="p">(</span><span class="nx">urlString</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">dnsRecord</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">dns</span><span class="p">.</span><span class="nx">lookup</span><span class="p">(</span><span class="nx">url</span><span class="p">.</span><span class="nx">hostname</span><span class="p">);</span>
  <span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">connection</span> <span class="o">=</span> <span class="nx">createConnection</span><span class="p">(</span>
      <span class="p">{</span><span class="na">port</span><span class="p">:</span> <span class="nx">url</span><span class="p">.</span><span class="nx">port</span> <span class="o">||</span> <span class="mi">80</span><span class="p">,</span> <span class="na">host</span><span class="p">:</span> <span class="nx">dnsRecord</span><span class="p">.</span><span class="nx">address</span><span class="p">},</span>
      <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">request</span> <span class="o">=</span> <span class="s2">`GET </span><span class="p">${</span><span class="nx">url</span><span class="p">.</span><span class="nx">pathname</span><span class="p">}</span><span class="s2"> HTTP/1.1`</span> <span class="o">+</span> <span class="dl">'</span><span class="se">\n</span><span class="dl">'</span> <span class="o">+</span>
          <span class="s2">`host: </span><span class="p">${</span><span class="nx">url</span><span class="p">.</span><span class="nx">hostname</span><span class="p">}</span><span class="s2">`</span> <span class="o">+</span> <span class="dl">'</span><span class="se">\n\n</span><span class="dl">'</span><span class="p">;</span>
        <span class="nx">connection</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="nx">request</span><span class="p">);</span>
        <span class="nx">readResponse</span><span class="p">(</span><span class="nx">connection</span><span class="p">,</span> <span class="nx">resolve</span><span class="p">);</span>
      <span class="p">}</span>
    <span class="p">);</span>
  <span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>]]></content><author><name></name></author><category term="javascript" /><category term="node" /><category term="http" /><summary type="html"><![CDATA[🎾 Implementing fetch in node 🐕]]></summary></entry><entry><title type="html">React tutorial in vanilla js 🍦</title><link href="/javascript/react/front-end/2022/08/15/react-tutorial-in-vanilla-js.html" rel="alternate" type="text/html" title="React tutorial in vanilla js 🍦" /><published>2022-08-15T15:07:00+00:00</published><updated>2022-08-15T15:07:00+00:00</updated><id>/javascript/react/front-end/2022/08/15/react-tutorial-in-vanilla-js</id><content type="html" xml:base="/javascript/react/front-end/2022/08/15/react-tutorial-in-vanilla-js.html"><![CDATA[<h1 id="react-tutorial-in-vanilla-js-">React tutorial in vanilla js 🍦</h1>

<p>The official react website has <a href="https://reactjs.org/tutorial/tutorial.html">a tutorial to teach people how to use react</a>. The idea is to build a tic tac toe (or naughts
and crosses) game. This game also recalls every move that was made and allows
you to return to any previous state.</p>

<p>I wanted to show not only that this exercise can be implemented relatively
easily in in vanilla js, but also that we can learn from the way react encourages
us to write code to write plain js in a nice way.</p>

<p>A word of warning, this code is not optimized for performance, I’m certainly not advocating that this quick and dirty solution can replace react in a large production based application with a lot of components. Hopefully it does make the reader think about whether they can justify the <strong>801</strong> npm dependencies that come bundled into create-react-app (the starting point for the react tutorial).</p>

<h2 id="setting-up-the-project">Setting up the project</h2>

<ol>
  <li>In a new directory create an <code class="language-plaintext highlighter-rouge">index.html</code> with the following contents. Note
the <code class="language-plaintext highlighter-rouge">defer</code> attribute on the <code class="language-plaintext highlighter-rouge">script</code> tag. This will defer the execution of
your script until the content has loaded. It’s a neat way out of having to do
something like <code class="language-plaintext highlighter-rouge">document.on('DOMContentLoaded', () =&gt; {/*your code*/}</code>.</li>
</ol>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;!DOCTYPE HTML&gt;</span>
<span class="nt">&lt;html&gt;</span>
    <span class="nt">&lt;head&gt;</span>
        <span class="nt">&lt;meta</span> <span class="na">http-equiv=</span><span class="s">"content-type"</span> <span class="na">content=</span><span class="s">"text/html; charset=utf-8"</span> <span class="nt">/&gt;</span>

        <span class="nt">&lt;title&gt;</span>Tic Tac Toe<span class="nt">&lt;/title&gt;</span>
        <span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"./style.css"</span> <span class="na">title=</span><span class="s">""</span> <span class="na">type=</span><span class="s">""</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;/head&gt;</span>
    <span class="nt">&lt;body&gt;</span>
        <span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"container"</span><span class="nt">&gt;&lt;/div&gt;</span>
        <span class="nt">&lt;script </span><span class="na">defer</span> <span class="na">src=</span><span class="s">"./script.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
    <span class="nt">&lt;/body&gt;</span>
<span class="nt">&lt;/html&gt;</span>
</code></pre></div></div>

<ol>
  <li>
    <p>Add a file called <code class="language-plaintext highlighter-rouge">style.css</code> with <a href="https://codepen.io/gaearon/pen/oWWQNa?editors=0100">the same content that react give you</a>.</p>
  </li>
  <li>
    <p>Add a file called <code class="language-plaintext highlighter-rouge">script.js</code> with <code class="language-plaintext highlighter-rouge">console.log('tic tac toe')</code></p>
  </li>
  <li>
    <p>Most systems should come with python installed, run <code class="language-plaintext highlighter-rouge">python -m http.server</code> to serve the current directory over http and visit http://localhost:8000 in your browser. Inspect the javascript console and make sure you see <code class="language-plaintext highlighter-rouge">tic tac toe</code> being logged to make sure your script is wired up correctly.</p>
  </li>
</ol>

<h2 id="making-the-grid">Making the grid</h2>

<p>When creating elements using vanilla javascript I like to make a small wrapper function to help to define my markup in a declarative way. This function does the job for this task.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">element</span> <span class="o">=</span> <span class="p">(</span><span class="nx">tag</span><span class="p">,</span> <span class="nx">options</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">el</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="nx">tag</span><span class="p">);</span>
  <span class="nx">options</span><span class="p">.</span><span class="kd">class</span> <span class="o">&amp;&amp;</span> <span class="nx">el</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">options</span><span class="p">.</span><span class="kd">class</span><span class="p">);</span>
  <span class="nx">el</span><span class="p">.</span><span class="nx">innerText</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">innerText</span> <span class="o">||</span> <span class="dl">''</span><span class="p">;</span>
  <span class="p">(</span><span class="nx">options</span><span class="p">.</span><span class="nx">children</span> <span class="o">||</span> <span class="p">[]).</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">child</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">el</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">child</span><span class="p">);</span>
  <span class="p">});</span>
  <span class="nb">Object</span><span class="p">.</span><span class="nx">entries</span><span class="p">(</span><span class="nx">options</span><span class="p">.</span><span class="nx">listeners</span> <span class="o">||</span> <span class="p">{}).</span><span class="nx">forEach</span><span class="p">(([</span><span class="nx">event</span><span class="p">,</span> <span class="nx">listener</span><span class="p">])</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">el</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="nx">event</span><span class="p">,</span> <span class="nx">listener</span><span class="p">);</span>
  <span class="p">});</span>
  <span class="k">return</span> <span class="nx">el</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<p>You can then use this to build your UI in a similar way with functions to define components as you might with <a href="">jsx</a>. This creates a status line and a 3 by 3 grid. Clicking on a square will fill it with an <code class="language-plaintext highlighter-rouge">X</code>.</p>

<p><img src="/assets/images/tic-tac-toe/tic-tac-toe-step-1.png" alt="grid with x" /></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">Status</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">element</span><span class="p">(</span><span class="dl">'</span><span class="s1">div</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
  <span class="na">innerText</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Next player: X</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">class</span><span class="p">:</span> <span class="dl">'</span><span class="s1">status</span><span class="dl">'</span>
<span class="p">});</span>

<span class="kd">const</span> <span class="nx">Square</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">element</span><span class="p">(</span><span class="dl">'</span><span class="s1">div</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
  <span class="na">class</span><span class="p">:</span> <span class="dl">'</span><span class="s1">square</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">listeners</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">click</span><span class="p">:</span> <span class="p">(</span><span class="nx">evt</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="nx">evt</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">innerText</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">X</span><span class="dl">'</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">});</span>

<span class="kd">const</span> <span class="nx">Row</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">element</span><span class="p">(</span><span class="dl">'</span><span class="s1">div</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
  <span class="na">class</span><span class="p">:</span> <span class="dl">'</span><span class="s1">board-row</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">children</span><span class="p">:</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="k">new</span> <span class="nb">Array</span><span class="p">(</span><span class="mi">3</span><span class="p">),</span> <span class="nx">Square</span><span class="p">)</span>
<span class="p">});</span>

<span class="kd">const</span> <span class="nx">render</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">game</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">container</span><span class="dl">'</span><span class="p">);</span>
  <span class="nx">game</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">Status</span><span class="p">());</span>
  <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">game</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">Row</span><span class="p">(</span><span class="nx">i</span><span class="p">));</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="reacting-to-state-changes">Reacting to state changes</h2>

<p>Something that react encourages us to do is to pull the state up as close to
the top of the application as possible. Let’s create a state object to hold
the current state of the grid and the status.
This is where the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy"><code class="language-plaintext highlighter-rouge">Proxy</code></a>
 class comes in handy. Proxy allows us to react to changes (or attempts to
 access) our state object. The second argument to the <code class="language-plaintext highlighter-rouge">Proxy</code> constructor
 specifies an event handler for whenever a property is set on our state object.
In this case, we set the property, then re-render the entire UI with the new state.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">stateHandler</span> <span class="o">=</span> <span class="p">{</span>
  <span class="kd">set</span><span class="p">(</span><span class="nx">obj</span><span class="p">,</span> <span class="nx">prop</span><span class="p">,</span> <span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">obj</span><span class="p">[</span><span class="nx">prop</span><span class="p">]</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
    <span class="nx">render</span><span class="p">();</span>
  <span class="p">}</span>
<span class="p">};</span>

<span class="kd">const</span> <span class="nx">state</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Proxy</span><span class="p">(</span>
  <span class="p">{</span>
    <span class="na">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Next player: X</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">grid</span><span class="p">:</span> <span class="p">[</span>
      <span class="p">[</span><span class="dl">''</span><span class="p">,</span> <span class="dl">''</span><span class="p">,</span> <span class="dl">''</span><span class="p">],</span>
      <span class="p">[</span><span class="dl">''</span><span class="p">,</span> <span class="dl">''</span><span class="p">,</span> <span class="dl">''</span><span class="p">],</span>
      <span class="p">[</span><span class="dl">''</span><span class="p">,</span> <span class="dl">''</span><span class="p">,</span> <span class="dl">''</span><span class="p">],</span>
    <span class="p">]</span>
  <span class="p">},</span>
  <span class="nx">stateHandler</span>
<span class="p">);</span>
</code></pre></div></div>
<p>Now of course this is not very efficient as we may be rendering everything just
to change a single square. However for such a small tree, this isn’t really
noticeable to the user.</p>

<p>Here’s what our <code class="language-plaintext highlighter-rouge">Square</code> component and our render method look like at this point.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">Square</span> <span class="o">=</span> <span class="p">(</span><span class="nx">rowIndex</span><span class="p">,</span> <span class="nx">squareIndex</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">element</span><span class="p">(</span><span class="dl">'</span><span class="s1">div</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
  <span class="na">class</span><span class="p">:</span> <span class="dl">'</span><span class="s1">square</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">innerText</span><span class="p">:</span> <span class="nx">state</span><span class="p">.</span><span class="nx">grid</span><span class="p">[</span><span class="nx">rowIndex</span><span class="p">][</span><span class="nx">squareIndex</span><span class="p">],</span>
  <span class="na">listeners</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">click</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">newGrid</span> <span class="o">=</span> <span class="nx">state</span><span class="p">.</span><span class="nx">grid</span><span class="p">;</span>
      <span class="nx">newGrid</span><span class="p">[</span><span class="nx">rowIndex</span><span class="p">][</span><span class="nx">squareIndex</span><span class="p">]</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">X</span><span class="dl">'</span><span class="p">;</span>
      <span class="c1">// re-set the grid property on the state proxy</span>
      <span class="nx">state</span><span class="p">.</span><span class="nx">grid</span> <span class="o">=</span> <span class="nx">newGrid</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">});</span>

<span class="kd">const</span> <span class="nx">render</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">game</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">container</span><span class="dl">'</span><span class="p">);</span>
  <span class="nx">game</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="dl">''</span><span class="p">;</span>
  <span class="nx">game</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">Status</span><span class="p">());</span>
  <span class="nx">state</span><span class="p">.</span><span class="nx">grid</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">_</span><span class="p">,</span> <span class="nx">rowIndex</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">game</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">Row</span><span class="p">(</span><span class="nx">rowIndex</span><span class="p">));</span>
  <span class="p">});</span>
<span class="p">}</span>

<span class="nx">render</span><span class="p">();</span>
</code></pre></div></div>

<p>Notice how in the <code class="language-plaintext highlighter-rouge">click</code> handler we are re-assigning <code class="language-plaintext highlighter-rouge">state.grid</code>. This will
cause our <code class="language-plaintext highlighter-rouge">set</code> handler to be called and thus re-render the entire grid with the updated
value.</p>

<h2 id="finding-a-winner">Finding a winner</h2>

<p>This calculateWinner function goes over the grid and checks if any of the players have a complete row or column. It’s not really the point of this post but it’s included for completeness.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">players</span> <span class="o">=</span> <span class="p">[</span><span class="dl">'</span><span class="s1">X</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">0</span><span class="dl">'</span><span class="p">];</span>
<span class="kd">const</span> <span class="nx">rowWins</span> <span class="o">=</span> <span class="p">(</span><span class="nx">player</span><span class="p">,</span> <span class="nx">rowIndex</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">state</span><span class="p">.</span><span class="nx">grid</span><span class="p">[</span><span class="nx">rowIndex</span><span class="p">].</span><span class="nx">every</span><span class="p">(</span><span class="nx">square</span>  <span class="o">=&gt;</span> <span class="nx">square</span> <span class="o">===</span> <span class="nx">player</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="nx">colWins</span> <span class="o">=</span> <span class="p">(</span><span class="nx">player</span><span class="p">,</span> <span class="nx">colIndex</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">state</span><span class="p">.</span><span class="nx">grid</span><span class="p">.</span><span class="nx">every</span><span class="p">(</span><span class="nx">row</span> <span class="o">=&gt;</span> <span class="nx">row</span><span class="p">[</span><span class="nx">colIndex</span><span class="p">]</span> <span class="o">===</span> <span class="nx">player</span><span class="p">)</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="nx">diagonalWin</span> <span class="o">=</span> <span class="p">(</span><span class="nx">player</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">state</span><span class="p">.</span><span class="nx">grid</span><span class="p">.</span><span class="nx">every</span><span class="p">((</span><span class="nx">_</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">grid</span><span class="p">[</span><span class="nx">index</span><span class="p">][</span><span class="nx">state</span><span class="p">.</span><span class="nx">grid</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">1</span> <span class="o">-</span> <span class="nx">index</span><span class="p">]);</span>
    <span class="k">return</span> <span class="nx">state</span><span class="p">.</span><span class="nx">grid</span><span class="p">[</span><span class="nx">index</span><span class="p">][</span><span class="nx">state</span><span class="p">.</span><span class="nx">grid</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">1</span> <span class="o">-</span> <span class="nx">index</span><span class="p">]</span> <span class="o">===</span> <span class="nx">player</span>
  <span class="p">})</span> <span class="o">||</span> <span class="nx">state</span><span class="p">.</span><span class="nx">grid</span><span class="p">.</span><span class="nx">every</span><span class="p">((</span><span class="nx">_</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">state</span><span class="p">.</span><span class="nx">grid</span><span class="p">[</span><span class="nx">index</span><span class="p">][</span><span class="nx">index</span><span class="p">]</span> <span class="o">===</span> <span class="nx">player</span>
  <span class="p">});</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="nx">isWinner</span> <span class="o">=</span> <span class="p">(</span><span class="nx">rowIndex</span><span class="p">,</span> <span class="nx">colIndex</span><span class="p">,</span> <span class="nx">player</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">rowWins</span><span class="p">(</span><span class="nx">player</span><span class="p">,</span> <span class="nx">rowIndex</span><span class="p">)</span> <span class="o">||</span> <span class="nx">colWins</span><span class="p">(</span><span class="nx">player</span><span class="p">,</span> <span class="nx">colIndex</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="nx">calculateWinner</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">for</span><span class="p">(</span><span class="kd">let</span> <span class="nx">rowIndex</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">rowIndex</span> <span class="o">&lt;</span> <span class="nx">state</span><span class="p">.</span><span class="nx">grid</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">rowIndex</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">colIndex</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">colIndex</span> <span class="o">&lt;</span> <span class="nx">state</span><span class="p">.</span><span class="nx">grid</span><span class="p">[</span><span class="nx">rowIndex</span><span class="p">].</span><span class="nx">length</span><span class="p">;</span> <span class="nx">colIndex</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">winner</span> <span class="o">=</span> <span class="nx">players</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="nx">player</span> <span class="o">=&gt;</span> <span class="nx">isWinner</span><span class="p">(</span><span class="nx">rowIndex</span><span class="p">,</span> <span class="nx">colIndex</span><span class="p">,</span> <span class="nx">player</span><span class="p">));</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">winner</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nx">winner</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>
  <span class="kd">const</span> <span class="nx">diagonalWinner</span> <span class="o">=</span> <span class="nx">players</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="nx">diagonalWin</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">diagonalWinner</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">diagonalWinner</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This means that by changing our <code class="language-plaintext highlighter-rouge">status</code> property on the <code class="language-plaintext highlighter-rouge">state</code> to keep track of the currentPlayer instead, we can now change the status line to indicate the current player and alternate turns between <code class="language-plaintext highlighter-rouge">0</code> and <code class="language-plaintext highlighter-rouge">X</code>. We now have a working, playable tic tac toe implementation.</p>

<p><img src="/assets/images/tic-tac-toe/tic-tac-toe-step-2.png" alt="grid with x and 0" /></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">state</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Proxy</span><span class="p">(</span>
  <span class="p">{</span>
    <span class="na">currentPlayer</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="na">grid</span><span class="p">:</span> <span class="p">[</span>
      <span class="p">[</span><span class="dl">''</span><span class="p">,</span> <span class="dl">''</span><span class="p">,</span> <span class="dl">''</span><span class="p">],</span>
      <span class="p">[</span><span class="dl">''</span><span class="p">,</span> <span class="dl">''</span><span class="p">,</span> <span class="dl">''</span><span class="p">],</span>
      <span class="p">[</span><span class="dl">''</span><span class="p">,</span> <span class="dl">''</span><span class="p">,</span> <span class="dl">''</span><span class="p">],</span>
    <span class="p">],</span>
    <span class="na">history</span><span class="p">:</span> <span class="p">[]</span>
  <span class="p">},</span>
  <span class="nx">stateHandler</span>
<span class="p">);</span>

<span class="kd">const</span> <span class="nx">Status</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">element</span><span class="p">(</span><span class="dl">'</span><span class="s1">div</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
  <span class="na">innerText</span><span class="p">:</span> <span class="nx">calculateWinner</span><span class="p">()</span> <span class="o">===</span> <span class="kc">null</span> <span class="p">?</span>
    <span class="s2">`Next player: </span><span class="p">${</span><span class="nx">players</span><span class="p">[</span><span class="nx">state</span><span class="p">.</span><span class="nx">currentPlayer</span> <span class="o">%</span> <span class="mi">2</span><span class="p">]}</span><span class="s2">`</span> <span class="p">:</span>
    <span class="s2">`Winner: </span><span class="p">${</span><span class="nx">winner</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
  <span class="na">class</span><span class="p">:</span> <span class="dl">'</span><span class="s1">status</span><span class="dl">'</span>
<span class="p">});</span>

<span class="kd">const</span> <span class="nx">Square</span> <span class="o">=</span> <span class="p">(</span><span class="nx">rowIndex</span><span class="p">,</span> <span class="nx">squareIndex</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">element</span><span class="p">(</span><span class="dl">'</span><span class="s1">div</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
  <span class="na">class</span><span class="p">:</span> <span class="dl">'</span><span class="s1">square</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">innerText</span><span class="p">:</span> <span class="nx">state</span><span class="p">.</span><span class="nx">grid</span><span class="p">[</span><span class="nx">rowIndex</span><span class="p">][</span><span class="nx">squareIndex</span><span class="p">],</span>
  <span class="na">listeners</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">click</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">newGrid</span> <span class="o">=</span> <span class="nx">state</span><span class="p">.</span><span class="nx">grid</span><span class="p">;</span>
      <span class="c1">// switch who's go it is based on the current player</span>
      <span class="nx">newGrid</span><span class="p">[</span><span class="nx">rowIndex</span><span class="p">][</span><span class="nx">squareIndex</span><span class="p">]</span> <span class="o">=</span> <span class="nx">players</span><span class="p">[</span><span class="nx">state</span><span class="p">.</span><span class="nx">currentPlayer</span> <span class="o">%</span> <span class="mi">2</span><span class="p">];</span>
      <span class="nx">state</span><span class="p">.</span><span class="nx">grid</span> <span class="o">=</span> <span class="nx">newGrid</span><span class="p">;</span>
      <span class="c1">// increment the current player</span>
      <span class="nx">state</span><span class="p">.</span><span class="nx">currentPlayer</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<h2 id="implementing-the-history">Implementing the history</h2>

<p>The history can be implemented using the same idea. We’ll add a <code class="language-plaintext highlighter-rouge">history</code> property to our <code class="language-plaintext highlighter-rouge">state</code> object and assign it to an empty array. Every time a move is made, in the <code class="language-plaintext highlighter-rouge">Square</code> click handler, we will add the previous state of the grid to the <code class="language-plaintext highlighter-rouge">history</code> array. One thing we need to be mindful of here is to take a deep copy of the grid rather than adding a reference to the grid array in the history.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">gridCopy</span> <span class="o">=</span> <span class="nx">state</span><span class="p">.</span><span class="nx">grid</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">x</span> <span class="o">=&gt;</span> <span class="nx">x</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">y</span> <span class="o">=&gt;</span> <span class="nx">y</span><span class="p">));</span>
<span class="nx">state</span><span class="p">.</span><span class="nx">history</span> <span class="o">=</span> <span class="p">[...</span><span class="nx">state</span><span class="p">.</span><span class="nx">history</span><span class="p">,</span> <span class="nx">gridCopy</span><span class="p">];</span>
</code></pre></div></div>

<p>We can then implement some new components to render the history and allow users to go back in time to any previous move in the game.</p>

<p><img src="/assets/images/tic-tac-toe/tic-tac-toe-step-3.png" alt="grid with history" /></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">Move</span> <span class="o">=</span> <span class="p">(</span><span class="nx">move</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">element</span><span class="p">(</span><span class="dl">'</span><span class="s1">li</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
  <span class="na">children</span><span class="p">:</span> <span class="p">[</span>
    <span class="nx">element</span><span class="p">(</span><span class="dl">'</span><span class="s1">button</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
      <span class="na">innerText</span><span class="p">:</span> <span class="nx">move</span> <span class="o">===</span> <span class="mi">0</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">Go to game start</span><span class="dl">'</span> <span class="p">:</span> <span class="s2">`Go to move #</span><span class="p">${</span><span class="nx">move</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
      <span class="na">listeners</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">click</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
          <span class="nx">state</span><span class="p">.</span><span class="nx">grid</span> <span class="o">=</span> <span class="nx">state</span><span class="p">.</span><span class="nx">history</span><span class="p">[</span><span class="nx">move</span><span class="p">];</span>
          <span class="nx">state</span><span class="p">.</span><span class="nx">history</span> <span class="o">=</span> <span class="nx">state</span><span class="p">.</span><span class="nx">history</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">move</span><span class="p">);</span>
        <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">})</span>
  <span class="p">]</span>
<span class="p">});</span>

<span class="kd">const</span> <span class="nx">History</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">element</span><span class="p">(</span><span class="dl">'</span><span class="s1">ol</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
  <span class="na">children</span><span class="p">:</span> <span class="nx">state</span><span class="p">.</span><span class="nx">history</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">_</span><span class="p">,</span> <span class="nx">i</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">Move</span><span class="p">(</span><span class="nx">i</span><span class="p">))</span>
<span class="p">});</span>
</code></pre></div></div>]]></content><author><name></name></author><category term="javascript" /><category term="react" /><category term="front-end" /><summary type="html"><![CDATA[React tutorial in vanilla js 🍦]]></summary></entry><entry><title type="html">Using migra for database migrations</title><link href="/postgresql/python/databases/2022/01/17/better-schema-migrations.html" rel="alternate" type="text/html" title="Using migra for database migrations" /><published>2022-01-17T20:48:12+00:00</published><updated>2022-01-17T20:48:12+00:00</updated><id>/postgresql/python/databases/2022/01/17/better-schema-migrations</id><content type="html" xml:base="/postgresql/python/databases/2022/01/17/better-schema-migrations.html"><![CDATA[<h1 id="using-migra-for-database-migrations">Using migra for database migrations</h1>

<p>Database schema migrations are a tough part of working with databases whether you let your framework (eg: rails, django) manage your schema or you choose to do it yourself. We opted for a simpler set of scripts that applied all the sql files in a <code class="language-plaintext highlighter-rouge">./db/migrations</code> directory in order to build up the schema. The directory listing looks something like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>000-baseline.sql
001-add-sample-table.sql
002-add-pack-table.sql
</code></pre></div></div>
<p>When you want to change/modify an object, you simply add a new sql file with the next number in the list to make your change.</p>

<p>Postgres offers an <a href="https://hub.docker.com/_/postgres">official docker image</a> to do exactly this task. We place the following <code class="language-plaintext highlighter-rouge">Dockerfile</code> in our <code class="language-plaintext highlighter-rouge">./db</code> directory.</p>

<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> postgres:10-alpine</span>
<span class="k">ADD</span><span class="s"> ./migrations /docker-entrypoint-initdb.d/</span>
<span class="k">ENV</span><span class="s"> POSTGRES_DB your_database_name</span>
</code></pre></div></div>

<p>This image creates a new postgresql server with a database called <code class="language-plaintext highlighter-rouge">your_database_name</code> and runs through the migrations in <code class="language-plaintext highlighter-rouge">/docker-entrypoint-initdb.d/</code> sequentially.</p>

<p>For local development, we use docker-compose to test our application with a fresh copy of our database schema in a known state.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3.4"</span>
<span class="na">services</span><span class="pi">:</span>
  <span class="na">db</span><span class="pi">:</span>
    <span class="na">build</span><span class="pi">:</span>
      <span class="na">context</span><span class="pi">:</span> <span class="s">./db</span>
  <span class="na">tests</span><span class="pi">:</span>
    <span class="na">build</span><span class="pi">:</span>
        <span class="na">context</span><span class="pi">:</span> <span class="s">.</span>
    <span class="na">depends_on</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">db</span>
    <span class="na">links</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">db</span>
    <span class="na">command</span><span class="pi">:</span> <span class="s">npm test</span>
</code></pre></div></div>

<p>This runs our tests against the freshly created local database.</p>

<p>Initially, when we were ready to go to the test environment we manually ran the latest migration, deployed our application and ran our smoke tests against it. If that went well, we repeated the process in production.</p>

<p>This worked up to a point, however, as our application and database grew, migrations became more tricky. We needed to think carefully about how they affected existing data, about users of specific database functions expecting a certain signature and generally about how to avoid downtime during migrations.</p>

<p>If migrations are tricky and need to be run in stages manually, you will undoubtedly sooner or later end up with diverging schemas where what’s in git doesn’t match what’s in the test environment and what’s in production.</p>

<p>This happened to us after releasing some new database orientated features and caused a couple of bugs in production that were hard to track down and were due to migrations being partially or not applied at all between environments.</p>

<p>To avoid this from happening again, we added <a href="https://pypi.org/project/migra/">migra</a> to our CI pipeline. Migra allows you to compare 2 database schemas and output the code required to make them identical. Think of it like a diff for database schemas. When we push a change to an environment, We compare the schema we’ve tested against in docker with the environment we’re targetting.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-compose run tests migra <span class="se">\</span>
  <span class="nt">--unsafe</span> <span class="se">\</span>
  <span class="nt">--with-privileges</span> <span class="se">\</span>
  postgresql://<span class="nv">$user</span>:<span class="nv">$password</span>@<span class="nv">$host</span>:<span class="nv">$port</span>/<span class="nv">$database</span> <span class="se">\</span>
  postgresql://postgres:password@db/<span class="nv">$database</span>
</code></pre></div></div>

<p>If they are not identical, the build fails and outputs the SQL code needed to bring the environment’s schema in line with what the code is expecting.</p>

<p>From there, we have the choice to run the output code in that environment or to structure the migrations differently to prevent data loss. Most of the time, the migration that was used to modify the docker image for the local tests can simply be used.</p>

<p>Once the migrations have been run in the target environment, the build can be rerun and will continue to deploy the application code now that the database schema is up to date. This ensures that our environments remain in sync with each other and what we are testing against.</p>]]></content><author><name></name></author><category term="postgresql" /><category term="python" /><category term="databases" /><summary type="html"><![CDATA[Using migra for database migrations]]></summary></entry></feed>