<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Martijn Arts]]></title><description><![CDATA[My personal tech blog, exploring predictable programming, next level automation, and the stabilisation of software as an engineering practice.]]></description><link>https://blog.martijnarts.com/</link><image><url>https://blog.martijnarts.com/favicon.png</url><title>Martijn Arts</title><link>https://blog.martijnarts.com/</link></image><generator>Ghost 5.79</generator><lastBuildDate>Wed, 21 Feb 2024 12:21:48 GMT</lastBuildDate><atom:link href="https://blog.martijnarts.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Hosting quickly 3 - A Neon database]]></title><description><![CDATA[<p>This is the third (or third-and-a-halfth) article in a series called &quot;Hosting quickly&quot;. I want to launch Rust apps quickly and often, and I want to have stable, common infrastructure for all of them.</p>
<ul>
<li><a href="https://blog.martijnarts.com/hosting-quickly-1-setting-up-terraform-github-and-1password/">Hosting quickly 1 - Setting up Terraform, Github, and 1Password</a></li>
<li><a href="https://blog.martijnarts.com/hosting-quickly-2-dioxus-to-the-web-on-fly-io/">Hosting quickly 2 -</a></li></ul>]]></description><link>https://blog.martijnarts.com/hosting-quickly-3/</link><guid isPermaLink="false">65d495e836b6240001e4e65a</guid><dc:creator><![CDATA[Martijn Arts]]></dc:creator><pubDate>Wed, 21 Feb 2024 12:20:04 GMT</pubDate><content:encoded><![CDATA[<p>This is the third (or third-and-a-halfth) article in a series called &quot;Hosting quickly&quot;. I want to launch Rust apps quickly and often, and I want to have stable, common infrastructure for all of them.</p>
<ul>
<li><a href="https://blog.martijnarts.com/hosting-quickly-1-setting-up-terraform-github-and-1password/">Hosting quickly 1 - Setting up Terraform, Github, and 1Password</a></li>
<li><a href="https://blog.martijnarts.com/hosting-quickly-2-dioxus-to-the-web-on-fly-io/">Hosting quickly 2 - Dioxus to the web on Fly.io</a></li>
<li><a href="https://blog.martijnarts.com/hosting-quickly-2-5-discovering-and-calling-a-backend-from-dioxus/">Hosting quickly 2.5 - Discovering and calling a backend from Dioxus</a></li>
</ul>
<p>To get to the point where I can develop my very first application off of Cochrane, I&apos;m adding a database to my setup. Keeping with the free tier, low setup goals here, I&apos;m using <a href="https://neon.tech">Neon</a>. Additionally, I want this to work with local development so we&apos;ll add a Docker Compose configuration!</p>
<h1 id="adding-neon-to-terraform">Adding Neon to Terraform</h1>
<p>There seem to be two Neon providers (<a href="https://github.com/kislerdm/terraform-provider-neon">1</a>, <a href="https://github.com/terraform-community-providers/terraform-provider-neon">2</a>), which are near feature parity with each other. The first one, by <a href="https://www.dkisler.com/">Dmitry Kisler</a> appears to be more active and more used, so let&apos;s go with that one.</p>
<p>A tiny bit of config needs to be added to <code>infra/main.tf</code> to get a functioning connection URL to a database:</p>
<pre><code class="language-hcl">resource &quot;neon_project&quot; &quot;db_project&quot; {
  name = var.github_name
  branch {
    name = &quot;main&quot;
    database_name = &quot;core&quot;
    role_name = &quot;backend&quot;
  }
}
</code></pre>
<p>And when that&apos;s working, we can add it to the environment variables for our backend:</p>
<pre><code class="language-hcl">resource &quot;fly_machine&quot; &quot;machine_backend&quot; {
  // ...
  env = {
    DATABASE_URL = neon_project.db_project.connection_uri
  }
}
</code></pre>
<h1 id="connecting-from-our-backend">Connecting from our backend</h1>
<p>Let&apos;s add two new files, <code>backend/src/database.rs</code> and <code>backend/src/config.rs</code> and add it to <code>main.rs</code> with a <code>mod</code> statements. Inside our <code>config.rs</code>, we&apos;ll load configuration from file or environment, and in <code>database.rs</code> we&apos;ll connect to the actual database.</p>
<p>In <code>config.rs</code> let&apos;s define our database configuration:</p>
<pre><code class="language-rust">#[derive(Deserialize, Getters)]
pub struct Config {
    #[getset(get = &quot;pub&quot;)]
    database: DatabaseConfig,
}

#[derive(Deserialize)]
pub struct DatabaseConfig {
    username: SecretString,
    password: SecretString,
    port: u16,
    host: String,
    database_name: String,
}

impl DatabaseConfig {
    pub fn as_connect_options(&amp;self) -&gt; PgConnectOptions {
        PgConnectOptions::new()
            .host(&amp;self.host)
            .username(self.username.expose_secret())
            .password(self.password.expose_secret())
            .port(self.port)
            .database(&amp;self.database_name)
    }
}
</code></pre>
<p>Two notable things here: the <code>Getters</code> and <code>getset</code> attributes, from the <a href="https://crates.io/crates/getset">getset</a> crate. This enforces the readonly nature of our <code>Config</code> struct: it should only ever be constructed, never modified. Secondly the <code>SecretString</code>s in the <code>DatabaseConfig</code>. This comes from <a href="https://crates.io/crates/secrecy">secrecy</a> and gives some stronger guarantees around application secrets, including making it harder to accidentally log them.</p>
<p>We&apos;ll load our config from environment only for now, using the excellent <a href="https://crates.io/crates/config">config</a> crate.</p>
<pre><code class="language-rust">pub fn get_configuration() -&gt; Result&lt;Config, config::ConfigError&gt; {
    let settings = config::Config::builder()
        .add_source(config::Environment::default().separator(&quot;__&quot;))
        .build()?;
    settings.try_deserialize::&lt;Config&gt;()
}
</code></pre>
<p>Connecting to the database is simple enough, just a few lines of code in <code>database.rs</code>:</p>
<pre><code class="language-rust">pub async fn connect_to_postgres(settings: &amp;DatabaseConfig) -&gt; Result&lt;PgPool, sqlx::Error&gt; {
    let connection = PgPool::connect_with(settings.as_connect_options()).await?;
    Ok(connection)
}
</code></pre>
<h1 id="running-a-query">Running a query</h1>
<p>Let&apos;s add a silly little query for now:</p>
<pre><code class="language-rust">async fn get_data(State(pool): State&lt;PgPool&gt;) -&gt; Json&lt;GetResult&gt; {
    let res = query!(&quot;SELECT 5 + 5 AS sum;&quot;)
        .fetch_one(&amp;pool)
        .await
        .unwrap();

    Json(GetResult {
        foo: res.sum.unwrap_or_default().to_string(),
    })
}

#[tokio::main]
async fn main() {
    let db_pool = connect_to_postgres(config.database())
        .await
        .unwrap();

    let state = ServerState::new(db_pool);

    // ...

    let app = Router::new()
        .route(&quot;/&quot;, get(get_data))
        .layer(cors)
        .with_state(state);
}
</code></pre>
<p>Passing state to an Axum router is sort of out-of-scope here, so I&apos;ll leave that as an exercise to the reader. Or for the reader to look up in <a href="https://github.com/martijnarts/cochrane">the code on Github</a>.</p>
<h1 id="local-development">Local development</h1>
<p>Right now, to test our connections I have to deploy it. But I&apos;d like to test it before. I can keep running Docker images manually, using <code>justfile</code> commands to string them together. That&apos;ll get complicated fast though, so maybe I should consider alternatives.</p>
<p>I&apos;m pretty unfamiliar with making these kind of complex services run in different environments, especially locally. I have some options, like Docker Compose or running Kubernetes locally, potentially with Helm. Some searching quickly shows that lots of people are excited about Docker Compose. Let&apos;s go with that!</p>
<p>We&apos;ll create an <code>local-dev/</code> folder, with a <code>justfile</code> and a <code>compose.yaml</code>:</p>
<pre><code class="language-justfile">start:
  docker-compose -f compose.yaml up --build

stop:
  docker-compose -f compose.yaml down
</code></pre>
<p>The <code>--build</code> flag for the <code>start</code> command is important! Without this, Docker will not try to rebuild your image, even if the filesystem has changed.</p>
<pre><code class="language-yaml">version: &apos;3.1&apos;

services:
  backend:
    build:
      context: ../
      dockerfile: backend/Dockerfile
    ports:
      - &quot;8002:3000&quot;
    depends_on:
      - postgres
    environment:
      - DATABASE__HOST=postgres
      - DATABASE__PORT=5432
      - DATABASE__USERNAME=postgres
      - DATABASE__PASSWORD=example
      - DATABASE__DATABASE_NAME=cochrane
  frontend:
    build:
      context: ../
      dockerfile: frontend/Dockerfile
    ports:
      - &quot;8001:8080&quot;
    environment:
      - &apos;DIOXUS_ENV={ &quot;backend_url&quot;: &quot;http://localhost:8002&quot; }&apos;
  postgres:
    image: &quot;postgres:16-alpine&quot;
    restart: always
    ports:
      - &quot;8101:5432&quot;
    environment:
      POSTGRES_PASSWORD: example
      POSTGRES_DB: cochrane
</code></pre>
<p>Some special things are worth mentioning! You can see we&apos;re setting up the configuration properties here using the <code>environment</code> property on the <code>backend</code> service, and pointing it to our Postgres instance. For each service that we have to build, we also set both a <code>build.context</code>,  relative to the <code>compose.yaml</code> itself, and then a <code>build.dockerfile</code> relative to <em>that context</em>.</p>
<p>But, to <em>compile</em> our sqlx queries, we need to have run <code>cargo sqlx prepare</code>. And for that, we need to configure our database. So let&apos;s set up a way that we can boot our Postgres machine by itself and run the command against it:</p>
<pre><code class="language-justfile">start-service service:
  docker-compose -f compose.yaml up -d {{service}}

stop-service service:
  docker-compose -f compose.yaml down {{service}}

sqlx-prepare: (start-service &quot;postgres&quot;)
  cd .. &amp;&amp; cargo sqlx prepare --database-url=postgres://postgres:example@localhost:8101/cochrane --workspace
</code></pre>
<p>Running <code>up</code> without the <code>-d</code> flag (for &quot;detach&quot;) will attach to the Docker container and log its outputs to the terminal. We want it to run in the background in this case.</p>
<p>This way we can boot one specific service, keeping in mind that the backend Docker image will not work without running <code>sqlx prepare</code> first. Then, using the appropriate Postgres URI, sqlx can connect to it and prepare its compilation files.</p>
<p>Now we&apos;re ready to run the magical <code>just local-dev/start</code>, and hit our frontend at <a href="https://localhost:8001/">https://localhost:8001/</a>. It should display <code>{&quot;foo&quot;:&quot;10&quot;}</code> on your frontpage. Awesome!</p>
<h1 id="fine-an-actual-query">Fine, an <em>actual</em> query</h1>
<p>To run a real query against a real database, we&apos;ll first want to set up sqlx migrations to actually create that table. Let&apos;s run <code>cargo sqlx migrate add init</code> to set up our first migration. Then we add to the justfile:</p>
<pre><code class="language-justfile">sqlx-migrate: (start-service &quot;postgres&quot;)
  cd .. &amp;&amp; cargo sqlx migrate run --database-url=postgres://postgres:example@localhost:8101/cochrane

sqlx-prepare: (start-service &quot;postgres&quot;) sqlx-migrate
  # ...
</code></pre>
<p>Let&apos;s fill our actual migration:</p>
<pre><code class="language-sql">CREATE TABLE test (
  id UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(),
  name TEXT NOT NULL
)
</code></pre>
<p>Now all you need to do is run <code>just local-dev/sqlx-migrate</code> and you&apos;ll see it migrating. We&apos;ve also added <code>sqlx-migrate</code> as a dependency to <code>sqlx-prepare</code>, so running that after creating a new migration will make sure everything&apos;s ready for building a database!</p>
<p>Let&apos;s use our table from the API:</p>
<pre><code class="language-rust">#[derive(Deserialize)]
struct PostRequest {
    name: String,
}

#[debug_handler]
async fn post_data(
    State(pool): State&lt;PgPool&gt;,
    Path(id): Path&lt;Uuid&gt;,
    Json(request): Json&lt;PostRequest&gt;,
) -&gt; StatusCode {
    query!(
        &quot;INSERT INTO test (id, name) VALUES ($1, $2) ON CONFLICT (id) DO UPDATE SET name = $2&quot;,
        id,
        request.name
    )
    .execute(&amp;pool)
    .await
    .unwrap();

    StatusCode::OK
}
</code></pre>
<p>I will, again, leave part of this to the reader. Specifically the part where you take this data out (the GET request), and adding this to the router. We now just need to add the usage of this to our frontend...</p>
<pre><code class="language-rust">async fn api_post(url: url::Url, uuid: uuid::Uuid, name: String) -&gt; () {
    let client = reqwest::Client::new();
    client
        .post(url.join(uuid.to_string().as_str()).unwrap())
        .json(&amp;PostRequest { name })
        .send()
        .await
        .map_err(|e| e.to_string())
        .unwrap();

    ()
}
</code></pre>
<p>And use it:</p>
<pre><code class="language-rust">    let set_name = move |_| {
        cx.spawn({
            to_owned![backend_url, uuid];

            let new_uuid = uuid::Uuid::new_v4();

            async move {
                api_post(backend_url, new_uuid, &quot;Foo bar&quot;.to_string()).await;
                uuid.set(Some(new_uuid));
            }
        })
    };
</code></pre>
<p>And before you know it... it works!</p>
<h1 id="lets-recap">Let&apos;s recap...</h1>
<p>Wow, we did a lot here! We tackled adding a Neon database to our Rust applications and streamlined our development with Docker Compose. With a real database connection, we can finally start building applications with Cochrane. Stay tuned for some first examples of that! &#x1F440;</p>
<p>In the upcoming post, I&apos;ll add a basic version of running background tasks to Cochrane. Keep coming back!</p>
]]></content:encoded></item><item><title><![CDATA[Hosting quickly 2.5 - Discovering and calling a backend from Dioxus]]></title><description><![CDATA[<ul>
<li><a href="https://blog.martijnarts.com/hosting-quickly-1-setting-up-terraform-github-and-1password/">Hosting quickly 1 - Setting up Terraform, Github, and 1Password</a></li>
<li><a href="https://blog.martijnarts.com/hosting-quickly-2-dioxus-to-the-web-on-fly-io/">Hosting quickly 2 - Dioxus to the web on Fly.io</a></li>
</ul>
<p>We should make the backend serve some API that we can hit from the frontend.</p>
<p>We&apos;ll turn the root endpoint in the backend into a JSON-returning endpoint:</p>]]></description><link>https://blog.martijnarts.com/hosting-quickly-2-5-discovering-and-calling-a-backend-from-dioxus/</link><guid isPermaLink="false">657ae9f4baf97300010aad97</guid><dc:creator><![CDATA[Martijn Arts]]></dc:creator><pubDate>Sun, 17 Dec 2023 07:54:52 GMT</pubDate><content:encoded><![CDATA[<ul>
<li><a href="https://blog.martijnarts.com/hosting-quickly-1-setting-up-terraform-github-and-1password/">Hosting quickly 1 - Setting up Terraform, Github, and 1Password</a></li>
<li><a href="https://blog.martijnarts.com/hosting-quickly-2-dioxus-to-the-web-on-fly-io/">Hosting quickly 2 - Dioxus to the web on Fly.io</a></li>
</ul>
<p>We should make the backend serve some API that we can hit from the frontend.</p>
<p>We&apos;ll turn the root endpoint in the backend into a JSON-returning endpoint:</p>
<pre><code class="language-rust">#[derive(Serialize)]
struct Response {
    name: String,
}

#[debug_handler]
async fn home() -&gt; Json&lt;Response&gt; {
    Json(Response {
        name: &quot;Hello, World!&quot;.to_string(),
    })
}
</code></pre>
<p>and then use that endpoint from the frontend:</p>
<pre><code class="language-rust">#[derive(Deserialize)]
struct Response {
    name: String,
}

pub async fn get_endpoint() -&gt; Result&lt;String, reqwest::Error&gt; {
    let url = format!(&quot;/topstories.json&quot;);
    let res = reqwest::get(url).await?.json::&lt;Response&gt;().await?;

    Ok(res.name)
}

fn app(cx: Scope) -&gt; Element {
    let response = use_future(cx, (), |_| get_endpoint());

    match response.value() {
        Some(Ok(name)) =&gt; cx.render(rsx! {
            h1 { &quot;{name}&quot; }
        }),
        Some(Err(_)) | None =&gt; cx.render(rsx! {
            h1 { &quot;Error!&quot; }
        }),
    }
}
</code></pre>
<h1 id="let-the-frontend-know">Let the frontend know</h1>
<p>There&apos;s a very simple reason that this is a separate article: I need to figure out how to make the frontend discover the base URI of the backend API. There are a few options:</p>
<ol>
<li>Compile it in, either from an environment variable or as a hardcoded string.</li>
<li>Serve it in configuration, like an <code>/env.json</code> file that can be loaded from the frontend or inserted into a script tag in the HTML.</li>
<li>Ensure it&apos;s on the same domain, so that the frontend can just call /api.</li>
</ol>
<p>I&apos;m not a huge fan of compiling it into the code: I&apos;d like to be able to use the same compiled code across different environments. Additional requests, like having an <code>env.json</code> or service discovery, will only slow down the page load. Routing is something I&apos;d like to avoid depending on.</p>
<p>So instead, let&apos;s insert any environment variables into the <code>index.html</code> at startup! We&apos;ll start by loading it in Dioxus. I&apos;ll leave defining the <code>EnvSettings</code> as an exercise to the reader:</p>
<pre><code class="language-rust">fn get_dioxus_env&lt;&apos;a&gt;(cx: &amp;Scope) -&gt; EnvSettings {
    std::env::var(&quot;DIOXUS_ENV&quot;).unwrap_or_default()
}
</code></pre>
<p>For running the frontend locally and through Docker, we&apos;ll set it through the Justfile. For the <code>docker-run</code> command, we&apos;ll also make sure to forward the backend&apos;s port:</p>
<pre><code class="language-justfile">run $DIOXUS_ENV=&apos;BACKEND_API_URL=&quot;https://localhost:3000&quot;&apos;: build
    dx serve --features ssr --hot-reload --platform desktop

docker-run: docker-build
    docker run -it -p 8081:8081 -p 8080:8080 -e BACKEND_API_URL=https://localhost:8081 cochrane-frontend:dev
</code></pre>
<div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">I&apos;ve hardcoded the environment for now. Ideally we&apos;ll want to load or parse it from some configuration file, so that we can make sure it&apos;s the same everywhere.</div></div><h1 id="call-the-api">Call the API</h1>
<p>To make this as simple as possible, let&apos;s create a special component that calls the API and returns either an error or the text response:</p>
<pre><code class="language-rust">#[derive(PartialEq, Props)]
struct QueryingTextProps {
    backend_url: url::Url,
}

#[allow(non_snake_case)]
fn QueryingText(cx: Scope&lt;QueryingTextProps&gt;) -&gt; Element {
    let result = use_future(cx, &amp;cx.props.backend_url, |val| async move {
        let res = call_api(val).await;
        res
    });

    cx.render(match result.value() {
        Some(Ok(s)) =&gt; rsx! { p { &quot;{s}&quot; } },
        Some(Err(e)) =&gt; rsx! { p { &quot;{e}&quot; } },
        None =&gt; rsx! { p { &quot;Loading...&quot; } },
    })
}
</code></pre>
<p>Note the <code>call_api</code> function that&apos;s undefined, we&apos;ll get there. There&apos;s some setup code here that&apos;s necessary for a Dioxus component: the <code>QueryingTextProps</code> struct to allow passing our API endpoint, the <code>use_future</code> call to actually call the <code>async fn</code> that we&apos;ll be defining. Otherwise it&apos;s fairly standard!</p>
<p>In fact, the <code>call_api</code> function is <em>super</em> simple. It&apos;s just a plain <code>reqwest</code> call. Of course, we could flesh this out by sprinkling in some <code>serde_json</code> and better error types, but this&apos;ll do for now:</p>
<pre><code class="language-rust">async fn call_api(url: url::Url) -&gt; Result&lt;String, String&gt; {
    let res = reqwest::get(url).await.map_err(|e| e.to_string())?;
    res.text().await.map_err(|e| e.to_string())
}
</code></pre>
<p>All that&apos;s left is actually using the component now:</p>
<pre><code class="language-rust">let EnvSettings { backend_url } = get_dioxus_env(&amp;cx);
</code></pre>
<p>And then add this to the returned rsx:</p>
<pre><code class="language-rust">rsx! { QueryingText { backend_url: backend_url } },
</code></pre>
<p>Voila! It&apos;s all hooked up.</p>
<h1 id="results">Results</h1>
<p>I&apos;ve intentionally left certain elements for the reader to implement. Specifically, the exact definition of <code>EnvSettings</code> and the intricate details of error handling and JSON processing within the <code>call_api</code> function are not covered. Also, getting the environment info from a dotenv file or other configuration would probably improve this.</p>
<p>Anyway, I think it works out to be a nice way of configuring your frontend Docker image without having to rebuild your Rust code for each environment.</p>
<p>Enjoy!</p>
]]></content:encoded></item><item><title><![CDATA[Hosting quickly 2 - Dioxus to the web on Fly.io]]></title><description><![CDATA[<p>This is the second article in a series called &quot;Hosting quickly&quot;. I want to launch Rust apps quickly and often, and I want to have stable, common infrastructure for all of them.</p>
<ul>
<li><a href="https://blog.martijnarts.com/hosting-quickly-1-setting-up-terraform-github-and-1password/">Hosting quickly 1 - Setting up Terraform, Github, and 1Password</a></li>
</ul>
<p>Today, let&apos;s build a</p>]]></description><link>https://blog.martijnarts.com/hosting-quickly-2-dioxus-to-the-web-on-fly-io/</link><guid isPermaLink="false">656ddd93e79c8d0001f21eb9</guid><dc:creator><![CDATA[Martijn Arts]]></dc:creator><pubDate>Wed, 06 Dec 2023 11:44:23 GMT</pubDate><content:encoded><![CDATA[<p>This is the second article in a series called &quot;Hosting quickly&quot;. I want to launch Rust apps quickly and often, and I want to have stable, common infrastructure for all of them.</p>
<ul>
<li><a href="https://blog.martijnarts.com/hosting-quickly-1-setting-up-terraform-github-and-1password/">Hosting quickly 1 - Setting up Terraform, Github, and 1Password</a></li>
</ul>
<p>Today, let&apos;s build a frontend and backend in Dioxus and Axum, respectively.</p>
<h1 id="the-frontend">The frontend</h1>
<p>Let&apos;s keep it extra simple and only create an example Dioxus frontend, for now. I want the newest and the hottest I can get, which seems to be <a href="https://dioxuslabs.com/learn/0.4/getting_started/fullstack">Dioxus Fullstack</a>. Let&apos;s set that up in a new <code>frontend/</code> directory.</p>
<p>I&apos;ll add the new <code>frontend/</code> package as a member to our root workspace. For good measure, I&apos;ve copied the <a href="https://dioxuslabs.com/learn/0.4/CLI/configure#config-example">example Dioxus.toml</a> into the <code>frontend/</code> folder. Finally, we&apos;ll create a <code>frontend/justfile</code> with two simple commands:</p>
<pre><code class="language-justfile">build:
    dx build --features web

run: build
    dx serve --features ssr --hot-reload --platform desktop
</code></pre>
<p>Simply run <code>just frontend/run</code> from the root and... it works! It&apos;s almost like magic! Moving on, let&apos;s deploy this to Fly.io. To do that, I&apos;ll add a Dockerfile:</p>
<pre><code class="language-Dockerfile">FROM rust:1.73-buster AS builder

WORKDIR /usr/src/cochrane

RUN rustup target add wasm32-unknown-unknown \
    &amp;&amp; cargo install dioxus-cli --version 0.4.1
COPY . .
RUN cd frontend \
    &amp;&amp; dx build --features web --platform web --release

FROM caddy:2.7.5-alpine

COPY --from=builder /usr/src/cochrane/frontend/dist /usr/share/caddy
</code></pre>
<p>This works ... sort of. We get a response with all the necessary files, but the page doesn&apos;t render anything. Dioxus&apos;s SSR server by default only listens to <code>127.0.0.1</code>, so we need to make sure it listens to all addresses:</p>
<pre><code class="language-rust">#[cfg(feature = &quot;ssr&quot;)]
fn main() {
    use tracing_subscriber;
    tracing_subscriber::fmt::init();

    LaunchBuilder::new(app)
        .addr(std::net::SocketAddrV4::new(std::net::Ipv4Addr::new(0, 0, 0, 0), 8080))
        .launch();
}

#[cfg(not(feature = &quot;ssr&quot;))]
fn main() {
    LaunchBuilder::new(app).launch();
}
</code></pre>
<p>This&apos;ll do it.</p>
<h1 id="the-backend">The backend</h1>
<p>Let&apos;s set up a simple Axum server with one endpoint. Eventually we might want to add the routes from Dioxus&apos;s <a href="https://dioxuslabs.com/learn/0.4/reference/fullstack/server_functions">server functions</a>, but we&apos;ll leave that for if I ever want to really use it.</p>
<p>This is super simple and we&apos;re purposefully keeping this super simple, so I&apos;ll speed through the steps here. We&apos;ll add a <code>backend/</code> folder with its own <code>Cargo.toml</code>, <code>justfile</code>, and <code>Dockerfile</code>. The <code>main.rs</code> will be a straight copy from <a href="https://docs.rs/axum/latest/axum/#example">the example in its docs</a>.</p>
<p>The <code>justfile</code>:</p>
<pre><code class="language-justfile">build:
    cargo build

run:
    cargo run

docker-build:
    cd .. &amp;&amp; docker build . -f backend/Dockerfile -t cochrane-backend:dev

docker-run: docker-build
    docker run -it -p 8081:3000 cochrane-backend:dev
</code></pre>
<p>And the <code>Dockerfile</code>:</p>
<pre><code class="language-Dockerfile">FROM rust:1.73-buster AS builder

WORKDIR /usr/src/cochrane

COPY . .
RUN cargo build --release -p backend

CMD [&quot;/usr/src/cochrane/target/release/backend&quot;]
</code></pre>
<p>Running <code>just backend/docker-run</code> and hitting <code>localhost:8081</code> works! All we need to do is add a little magic Hyper incantation to allow the server to shut down nicely. We can lift that from <a href="https://hyper.rs/guides/0.14/server/graceful-shutdown/">the Hyper docs</a>.</p>
<h1 id="pushing-this-to-flyio">Pushing this to Fly.io</h1>
<p>Fly.io has recently <a href="https://github.com/fly-apps/terraform-provider-fly/commit/ee2a866bf9d0f782fc1e245ed1ee800817909a46">deprecated</a> their Terraform provider, but I&apos;m insistent on using Fly.io <em>and</em> Terraform (for now). I&apos;ll use <a href="https://github.com/pi3ch/terraform-provider-fly">a lightly-maintained fork</a> for now. There&apos;s a few steps to getting this working. First, I&apos;ll have to build the Docker images in Github Actions and push them to <a href="https://fly.io/docs/flyctl/auth-docker/">the Fly registry</a>. Then we&apos;ll need a Terraform configuration for <a href="https://registry.terraform.io/providers/pi3ch/fly/latest/docs/resources/app">a Fly app</a> and <a href="https://registry.terraform.io/providers/pi3ch/fly/latest/docs/resources/machine">machine</a></p>
<h2 id="preparing-our-fly-apps">Preparing our Fly apps</h2>
<p>We first need to add a Fly app to be able to even push the Docker images. I&apos;ll do this through adding this to our Terraform file:</p>
<pre><code class="language-terraform">variable &quot;fly_token&quot; {
  type        = string
  description = &quot;A Fly.io API token with access to the right organization&quot;
}

terraform {
  required_providers {
    // ...

    fly = {
      source = &quot;pi3ch/fly&quot;
      version = &quot;0.0.24&quot;
    }
  }
}

provider &quot;fly&quot; {
  fly_api_token = var.fly_token
}

resource &quot;fly_app&quot; &quot;app_backend&quot; {
  name = &quot;${var.github_name}-backend&quot;
}

resource &quot;fly_app&quot; &quot;app_frontend&quot; {
  name = &quot;${var.github_name}-frontend&quot;
}
</code></pre>
<p>As well as adding <code>fly_token = &quot;op://cochrane/fly/token&quot;</code> to our <code>terraform.tfvars.tpl</code>. Now we just need to run <code>just --dotenv-filename .just-env infra/apply</code> to actually create the apps.</p>
<h2 id="building-and-pushing-the-docker-image">Building and pushing the Docker image</h2>
<p>Let&apos;s start! I&apos;ve created a Fly account, and gotten myself an access token that I can use to publish to the Docker registry. Since Github can load the secrets from the 1Password vault directly, I don&apos;t need to add this token as an Actions Secret, just to my vault.</p>
<p>We&apos;ll add a second Github Workflow file, let&apos;s call it <code>build.yaml</code>, that builds and pushes the Docker images.. That&apos;s the easy part, so I&apos;m going to start with authenticating with Fly:</p>
<pre><code class="language-yaml">name: Build and deploy

on:
  workflow_dispatch:
  pull_request:
    paths:
      - &apos;frontend/**.rs&apos;
      - &apos;frontend/Cargo.toml&apos;
      - &apos;frontend/Dockerfile&apos;
      - &apos;backend/**.rs&apos;
      - &apos;backend/Cargo.toml&apos;
      - &apos;backend/Dockerfile&apos;

jobs:
  plan:
    runs-on: ubuntu-latest
    steps:
      - name: Get Fly token
        uses: 1password/load-secrets-action@v1
        with:
          export-env: true
        env:
          OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT }}
          FLY_ACCESS_TOKEN: op://cochrane/fly/token

      - uses: superfly/flyctl-actions/setup-flyctl@master
      - name: Authenticate Fly registry
        run: flyctl auth docker
</code></pre>
<p>Then we simply add the Docker <code>build-and-push</code> action steps to the end:</p>
<pre><code class="language-yaml">      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          push: true
          file: frontend/Dockerfile
          tags: registry.fly.io/cochrane-frontend:latest

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          push: true
          file: backend/Dockerfile
          tags: registry.fly.io/cochrane-backend:latest
</code></pre>
<p>Note we don&apos;t need Docker&apos;s regular username/app syntax here, just the app name.</p>
<div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x2705;</div><div class="kg-callout-text">I have to change the hardcoded cochrane reference in this workflow file for the repository name, probably... But it&apos;s hardcoded in my tfvars also, so maybe it&apos;s okay.</div></div><h2 id="configuring-our-fly-machines">Configuring our Fly machines</h2>
<p>All we need to do to get the app actually running on Fly is create an IP (for the traffic to be routed to) and a machine. The IP is easy:</p>
<pre><code>resource &quot;fly_ip&quot; &quot;ip_backend&quot; {
  app = fly_app.app_backend.name
  type = &quot;v4&quot;
}
</code></pre>
<p>The only sort of complicated thing about the machine itself is that you have to set the image to point specifically at Fly&apos;s own registry, and you have to configure the ports appropriately:</p>
<pre><code class="language-terraform">resource &quot;fly_machine&quot; &quot;machine_backend&quot; {
  app = fly_app.app_backend.name
  image = &quot;registry.fly.io/${var.github_name}-backend:latest&quot;
  region = &quot;iad&quot;
  services = [
    {
      ports = [
        {
          port = 443
          handlers = [&quot;tls&quot;, &quot;http&quot;]
        },
        {
          port = 80
          handlers = [&quot;http&quot;]
        }
      ]
      internal_port = 3000
      protocol = &quot;tcp&quot;
    }
  ]
}
</code></pre>
<p>After running <code>just --dotenv-filename .just-env infra/apply</code> You have a working service! You can find the IP to hit by running <code>echo &quot;fly_ip.ip_backend&quot; | terraform console -var-file=terraform.tfvars</code> in the <code>infra/</code> directory, or you can find the app url in your Fly dashboard.</p>
<p>Now copy this over for the frontend and... shock! It&apos;s running!</p>
<h1 id="the-end">The End</h1>
<p>Well... for now. We haven&apos;t actually made the frontend and backend talk with each other yet, nor are they doing anything useful. Up next is a quick &quot;intermission&quot; post on the first, and then we&apos;re deploying and configuring a Postgres database.</p>
<p>Be sure to subscribe to keep up, and check out <a href="https://github.com/martijnarts/cochrane">cochrane</a> on Github.</p>
]]></content:encoded></item><item><title><![CDATA[tstate - strongly typed Typescript state machines]]></title><description><![CDATA[<p>If you read <a href="https://blog.martijnarts.com/predictable-programming-3-using-xstate/">this</a> <a href="https://blog.martijnarts.com/testing-complex-xstate-machines/">blog</a> or <a href="https://martijnarts.com/atoms/State-machines-in-frontend-dev">my digital garden</a> you&apos;ll know I like working with state machines in the frontend. There&apos;s plenty of reasons for this, but the two main ones are that user interaction maps really well to state machines, and so do shared background</p>]]></description><link>https://blog.martijnarts.com/tstate-strongly-typed-typescript-state-machines/</link><guid isPermaLink="false">6553460599a4280001bdea8f</guid><dc:creator><![CDATA[Martijn Arts]]></dc:creator><pubDate>Fri, 01 Dec 2023 17:12:26 GMT</pubDate><content:encoded><![CDATA[<p>If you read <a href="https://blog.martijnarts.com/predictable-programming-3-using-xstate/">this</a> <a href="https://blog.martijnarts.com/testing-complex-xstate-machines/">blog</a> or <a href="https://martijnarts.com/atoms/State-machines-in-frontend-dev">my digital garden</a> you&apos;ll know I like working with state machines in the frontend. There&apos;s plenty of reasons for this, but the two main ones are that user interaction maps really well to state machines, and so do shared background processes like interactions with APIs or local storage.</p>
<p>Anyway, <a href="https://xstate.js.org/">XState</a> is good for this. However, I don&apos;t think it&apos;s good enough. Its typegen feels like a dirty fix, it doesn&apos;t strongly type its context per state, and its typings are very complicated to use<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>.</p>
<p>So I&apos;m creating <a href="https://github.com/martijnarts/tstate">tstate</a>, a codegenless, very strictly typed, pure Typescript, composable state machine library. It&apos;s based on some of my learnings from using XState quite extensively in production applications, and some of my dreams of what I want libraries to be.</p>
<h1 id="how-to-use-tstate">How to use tstate</h1>
<p>It&apos;s early days. Importantly, I&apos;m still working on getting context to work ergonomically. However, as a minimalist state machine library it actually works. Here&apos;s some examples pulled from the tests:</p>
<pre><code class="language-typescript">const machine = createStateMachine(
  {
    init: createStateDefinition({
      on: {
        foo: {
          target: &quot;bar&quot;,
        },
        bar: {
          target: &quot;init&quot;,
        },
      },
    }),
    bar: createStateDefinition({
      on: {
        baz: {
          target: &quot;init&quot;,
        },
      },
    }),
  },
  &quot;init&quot;
);
</code></pre>
<p>The <code>machine</code> variable is an instance of the machine, in its initial <code>init</code> state. It has a <code>send</code> function that accepts valid events for that state (in this case <code>foo</code> and <code>bar</code>):</p>
<pre><code class="language-typescript">const res = machine.send(&quot;foo&quot;);
expect(res.value).toEqual(&quot;bar&quot;);
</code></pre>
<p>You&apos;ll see that <code>.send(&quot;foo&quot;)</code> returns a new machine instance that&apos;s in the target state (<code>bar</code>). This, again, is strictly typed:</p>
<pre><code class="language-typescript">const res2 = res.send(&quot;baz&quot;);
expect(res2.value).toEqual(&quot;init&quot;);
</code></pre>
<h1 id="type-architecture">Type architecture</h1>
<p>In the tstate finite state machine, typing is enforced from the inside out, starting at the <code>createStateDefinition</code> function. This function defines a single state and its transitions in your state machine. It looks something like this:</p>
<pre><code class="language-typescript">function createStateDefinition&lt;const D extends StateDefinition&lt;&#x2026;&gt;&gt;(definition: D) {
  return definition;
}
</code></pre>
<p>The function takes the cont generic parameter of type <code>StateDefinition</code>. This generic should always be inferred by Typescript, and it&apos;ll enforce const typing o our output <code>StateDefinition</code> type so that the next function can use that information to enforce its own typings.</p>
<p>That function is <code>createStateMachine</code>. This is where you define a map of all your state names to their respective <code>StateDefinition</code> objects. Looks (a little) like this:</p>
<pre><code class="language-typescript">function createStateMachine&lt;const D extends MachineDefinition&lt;&#x2026;&gt;&gt;(config: D) {
  // State machine logic here
}
</code></pre>
<p>Its <code>MachineDefinition</code> type (again <code>const</code>) shares some generics with <code>StateDefinition</code>. It&apos;ll only accept <code>StateDefinition</code>s that use the states listed as keys of the <code>MachineDefinition</code> object. This ensures you cannot define a transition into a non-existent state.</p>
<p>While the first function is basically a simple passthrough that ensures ergonomic strict typing, this second function actually creates an state machine with a current state (the initial state) and a <code>.send</code> method to transition to other states. This method only accepts valid events.</p>
<h1 id="whereto-next">Whereto next?</h1>
<p>Up next is <a href="https://github.com/martijnarts/tstate/pull/1">context</a>. I&apos;m working on adding strong typing of context next to states. This means that when you&apos;ve asserted that a machine is in a certain state, you&apos;ll also know the shape of the context. I think this is a significant improvement over XState&apos;s status quo<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup>. To do this, I also need to build transition actions to mutate from the current context type into the new type.</p>
<p>After that I have a short list of things I want to work on:</p>
<ul>
<li>&quot;Nested&quot; states: in reality, this should be connector utilities between separately defined machines. I think it&apos;ll be much simpler, possibly at the loss of some ergonomics.</li>
<li>Utilities for connections with other state machines, like promises, callback functions, and observables.</li>
<li>Integrations with React and associated.</li>
</ul>
<p>If you have opinions about these, feel free to hit me up in <a href="https://github.com/martijnarts/tstate/discussions/new?category=general">Github Discussions</a></p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>Honestly, I might be wrong on any or all of these... except typegen. And typegen is enough of an issue for me to justify this post. <a href="#fnref1" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
<li id="fn2" class="footnote-item"><p>In XState, the type of the context is global for the entire machine. That means that if you&apos;re dealing with loading and ready states, you still have to do <code>!= null</code> checks or <code>!</code> overrides wherever you&apos;re using the context. <a href="#fnref2" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title><![CDATA[Testing complex XState machines]]></title><description><![CDATA[<p>I love using state machines in my frontends, and the current winner library to do it is XState. State machines (or actually state charts) can get quite complex, and like with everything I&apos;ve developed some specific ways to test it. I&apos;ve collected some guidelines and tricks</p>]]></description><link>https://blog.martijnarts.com/testing-complex-xstate-machines/</link><guid isPermaLink="false">65295ac485e3d400014dc4fe</guid><dc:creator><![CDATA[Martijn Arts]]></dc:creator><pubDate>Fri, 20 Oct 2023 10:04:18 GMT</pubDate><content:encoded><![CDATA[<p>I love using state machines in my frontends, and the current winner library to do it is XState. State machines (or actually state charts) can get quite complex, and like with everything I&apos;ve developed some specific ways to test it. I&apos;ve collected some guidelines and tricks I use in testing my XState machines, and I&apos;ll eternalise them here. For posterity.</p>
<h1 id="simple-machines-little-to-test">Simple machines, little to test</h1>
<p>First and foremost: keep your machines small. Nested parallel machines with lots of interdependent states and events are complicated to test not because of any technological reasons, but because the machines themselves are complicated. Small machines will be significantly easier to test.</p>
<p>Instead of nesting parallel machines, see if you can get away with defining the machines separately. You can invoke child machines from the parent and pass messages back and forth. There&apos;s a little more work involved, maybe, but your machines will be much more constrained.</p>
<h1 id="replacing-actions-and-services-wont-do-the-job">Replacing actions and services won&apos;t do the job</h1>
<p>This might be a controversial take, but I almost <em>never redefine actions or services with <code>.withConfig</code></em>. I like to test the machine as a whole, including the internal logic of actions and services and, importantly, the way that they affect the machine.</p>
<p>Instead I mock the side effects. Usually, this means <code>jest.mock</code>ing the function. I don&apos;t like mocking external things though, so I actually ended up providing the functions through the machine&apos;s <code>Context</code><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>. This makes it super easy to inject mocks that to test specific behaviour.</p>
<p>Looks like this:</p>
<pre><code class="language-typescript">const Machine = createMachine({
  schema: {} as {
    context: {
      doAThing: (foo: string) =&gt; number;
    };
  },
  // ... ommitted
});

it(&apos;does a thing&apos;, async () =&gt; {
  const doAThingMock = jest.fn&lt;
    // The type parameters aren&apos;t absolutely necessary in this example, but
    // they&apos;re useful when creating mock implementations.
    ReturnType&lt;ContextFrom&lt;typeof Machine&gt;[&apos;doAThing&apos;]&gt;,
    Parameters&lt;ContextFrom&lt;typeof Machine&gt;[&apos;doAThing&apos;]&gt;
  &gt;();

  const service = interpret(Machine.withContext({ doAThing: doAThingMock }));
  service.start();

  await waitFor(service, (state) =&gt; state.matches(&apos;...&apos;));
  expect(doAThingMock).toHaveBeenLastCalledWith(/* ... */);
});
</code></pre>
<h1 id="builder-pattern-to-the-rescue">Builder pattern to the rescue</h1>
<p>I&apos;m a sucker for a good <code>XYZBuilder</code> in tests. A good builder allows you to define only the properties relevant for the test, where the others have sane but mostly no-op defaults. I started out creating classes for each machine, but got annoyed at the boilerplate quickly. I tried out <a href="https://github.com/vincent-pang/builder-pattern/">builder-pattern</a> but eventually built <a href="https://github.com/martijnarts/tsbuilder">TSBuilder</a>.</p>
<p>TSBuilder allows easily setting up a type-safe builder. I&apos;m not entirely content with the API yet (especially the typings), but it does the job better than custom classes for now.</p>
<pre><code class="language-typescript">const MachineBuilder = TSBuilder&lt;
  ContextFrom&lt;typeof Machine&gt;,
  typeof Machine,
  never
&gt;(
  {
    prop: () =&gt; &apos;defaultValue&apos;,
  },
  (ctx) =&gt; Machine.withConfig(ctx),
);


const machine = MachineBuilder.withProp(&apos;otherValue&apos;)[Build]().result;
const service = interpret(machine);
</code></pre>
<p>Please open issues and PRs if you don&apos;t like things about TSBuilder, I welcome feedback!</p>
<h1 id="actorrefs">ActorRefs</h1>
<p>This is new to me and a bit of a work in progress. Sometimes you need to depend on, spawn, or reference other machines. This is extremely useful in connecting multiple independent processes, but quite difficult to test. Primarily you&apos;ll want to test one of the machines in isolation. Actually testing the machines altogether will be a bigger and more complex test, and as such you&apos;ll want to reduce the amount of tests at this level.</p>
<p>However, replacing machines through <code>createMachine</code>ing simpler, dumber versions of the original can be a big hassle. Most of the time I only need the nested machine to respond to a given action or send an event to its parent at a certain point. I ended up directly implementing <code>ActorRefFrom&lt;Machine&gt;</code>:</p>
<pre><code class="language-typescript">class MockMachine implements ActorRefFrom&lt;Machine&gt; {
  public readonly id = &apos;Machine&apos;;
  private _state: StateFrom&lt;typeof Machine&gt;[&apos;value&apos;] =
    &apos;idle&apos;;
  private _context: Context;
  private _listeners: Array&lt;{ func: Listener; id: string }&gt; = [];

  constructor(context: Context) {
    this._context = context;
  }

  getSnapshot() {
    return {
      context: this._context,
      value: this._state,
    } as StateFrom&lt;Machine&gt;;
  }

  unsubscribe(id: string) {
    this._listeners = this._listeners.filter((l) =&gt; l.id !== id);
  }

  subscribe(subscriber: Listener | { next: Listener }): Subscription {
    const listener = {
      func: &apos;next&apos; in subscriber ? subscriber.next : subscriber,
      id: v4(),
    };
    this._listeners.push(listener);

    return {
      unsubscribe: () =&gt; {
        this.unsubscribe(listener.id);
      },
    };
  }

  send(
    event:
      | EventFrom&lt;Machine&gt;[&apos;type&apos;]
      | EventFrom&lt;Machine&gt;,
  ) {
    const parsedEvent = typeof event === &apos;string&apos; ? { type: event } : event;
    this._listeners.forEach((l) =&gt;
      l.func({
        ...this.getSnapshot(),
        event: parsedEvent as EventFrom&lt;Machine&gt;,
      } as StateFrom&lt;Machine&gt;),
    );
  }

  get state() {
    return this.getSnapshot();
  }

  [Symbol.observable]() {
    return {} as InteropSubscribable&lt;StateFrom&lt;Machine&gt;&gt;;
  }
}
</code></pre>
<p>As you can probably tell, I don&apos;t actually implement the full behaviour of the <code>ActorRef</code>. It seems to work though, and Typescript believes it. Currently, this isn&apos;t generic but it should be easy enough. Pass it the correct types and you can then send certain events from the machine with <code>machine.send({ type: &apos;ACTION&apos; });</code>. It&apos;s similar to XState&apos;s <a href="https://stately.ai/docs/actors#empty-actors">empty actor</a> but you get more control over the snapshot.</p>
<p>I do think this could benefit from a generalised library. Maybe I&apos;ll build on eventually.</p>
<h1 id="concluding">Concluding...</h1>
<p>That&apos;s it for now. I realize that my specific testing strategies may be controversial. Dependency injection could be replaced with mocking, for one. However, it comes from some amount of experience, and I haven&apos;t seen anyone else&apos;s approaches to this.</p>
<p>Happy to discuss though, of course!</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>This is called dependency injection, by the way. <a href="#fnref1" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title><![CDATA[Experiments in recreating Rust's try operator in Typescript]]></title><description><![CDATA[<p>I love <a href="https://github.com/supermacro/neverthrow">neverthrow</a>, <a href="https://gcanti.github.io/fp-ts/modules/Either.ts.html">io-ts&apos; Either</a>, or a homemade Result library as much as anyone, but I do miss the <a href="https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator"><code>?</code>-operator</a> in Typescript.</p>
<p>This is my attempt at recreating it<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>.</p>
<h1 id="the-basic-idea">The basic idea</h1>
<p>There are two core things that Rust&apos;s <code>?</code>-operator allows us to do: early return</p>]]></description><link>https://blog.martijnarts.com/experiments-in-recreating-rusts-try-operator-in-typescript/</link><guid isPermaLink="false">65149518c427ce0001ee2cb0</guid><dc:creator><![CDATA[Martijn Arts]]></dc:creator><pubDate>Wed, 18 Oct 2023 10:17:10 GMT</pubDate><content:encoded><![CDATA[<p>I love <a href="https://github.com/supermacro/neverthrow">neverthrow</a>, <a href="https://gcanti.github.io/fp-ts/modules/Either.ts.html">io-ts&apos; Either</a>, or a homemade Result library as much as anyone, but I do miss the <a href="https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator"><code>?</code>-operator</a> in Typescript.</p>
<p>This is my attempt at recreating it<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>.</p>
<h1 id="the-basic-idea">The basic idea</h1>
<p>There are two core things that Rust&apos;s <code>?</code>-operator allows us to do: early return on errors and (as a result) chain <code>Result</code> types as if they&apos;re always <code>Ok</code>s. Let&apos;s get the first part out of the way: we cannot replicate inline early returns in Typescript.</p>
<p>For the second part, any Javascript aficionado might suggest that this already exists. We already have a <code>?</code> chaining operator in JS/TS. However, that works only for nullish values, which we actually want to allow.</p>
<p>We can sort of create a new operator though.</p>
<h1 id="the-spider-operator">The spider &quot;operator&quot;</h1>
<p>Attempt number one is based on Javascript&apos;s <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">Proxy</a> object, which allows us to override things like getters and setters. We&apos;ll build a spider operator <code>&#x10CF1;</code><sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup> that allows us to operate on the potential error state of a Result type:</p>
<pre><code class="language-typescript">type MyObj = { bar: { foo: String } };

const ok1 = ok&lt;MyObj, Error&gt;({ bar: { foo: &quot;it&apos;s ok&quot; } });
const mappedOk = ok1[&#x10CF1;].bar[&#x10CF1;].foo.map(() =&gt; &quot;it&apos;s not ok&quot; as String);

console.assert(&#x10CF1; in mappedOk);
console.assert(mappedOk.unwrapOk() === &quot;it&apos;s not ok&quot;);

const error1 = err&lt;MyObj, Error&gt;(new Error(&quot;hello world&quot;));
const mappedError = error1.mapErr(() =&gt; new Error(&quot;other world&quot;))[&#x10CF1;].bar;

console.assert(&#x10CF1; in mappedError);
console.assert(mappedError.unwrapErr().message === &quot;other world&quot;);
</code></pre>
<p>I went into this expecting this to be an entirely ridiculous API, but now that I&apos;ve played with it a little bit it turns out to... not be that crazy, maybe? It differs slightly from Rust: because we cannot do early returns, we have found a slightly nicer way of chaining on properties than simply chaining maps like <code>.map(val =&gt; val.prop).map(&#x2026;)</code>.</p>
<p>Let&apos;s build it! Obviously, we&apos;ll need to define the spider operator<sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup>:</p>
<pre><code class="language-typescript">const &#x10CF1; = Symbol.for(&quot;SPIDER_OPERATOR&quot;);
</code></pre>
<p>Then I&apos;ll define the <code>Result</code> type that both the <code>ok</code> and <code>err</code> classes will implement:</p>
<pre><code class="language-typescript">interface Result&lt;
  // these types need to extend object, or they won&apos;t have props to access.
  Ok extends object,
  Err extends object
&gt; {
  map&lt;NewOk extends object&gt;(cb: (e: Ok) =&gt; NewOk): Result&lt;NewOk, Err&gt;;
  mapErr&lt;NewErr extends object&gt;(cb: (e: Err) =&gt; NewErr): Result&lt;Ok, NewErr&gt;;
  unwrapOk(): Ok | never;
  unwrapErr(): Err | never;
  [&#x10CF1;]: {
    // make sure that any accessed prop is wrapped as a result also
    [Prop in keyof Ok]: Ok[Prop] extends object ? Result&lt;Ok[Prop], Err&gt; : never;
  };
}
</code></pre>
<p>For this blog, let&apos;s define a small part the <code>ok</code> type only. The full version is linked in <a href="https://www.typescriptlang.org/play?#code/MYewdgzgLgBAZgJxAWwAoIJbI1DA3AUwBUQB5AIwCsDhYBeGACjwEMAbAVwIC4YOwA1mBAB3MAEpeIKjXoA+GAG8AsACgYGmBBE5gACyZQAngAcCIODFacC4pWs2OYwFhAIwARNExgA5h+4HJ2CEAigOBDAYMAIRGABlKB9fZnYucQBuIOCXN08wDmRyAgQA7ODNUPDI6NiYADlC4oRUm0zyx1z3D3IQEDYCFjAy9QrHKoiomLiAIT6Bodb0rNGc127yDF8MMCgRsccoPSQ46ZgAUQQkFo8XMGFYCA4TExAEWE3t3Y921acuzxwfi0DDgfYHDQTGpnABiwNw4CWBAAdFAQIlkoxxL8xgCvEYiv1wRCjidanFLtdGLchg8tM9Xu8tATemwfitcetPNJqLRiQcMJYkTA6AwCmw2HYVH8IRpSaJyRcrm9qXc6U8Xm9YOK2TjZQBfDoVKFRaxcDmaQ2qcry051Skqjz8ISiU1pdzGMzstRWtRqUCQWCAYA3AI87IoSLP6yLgjviqAAkgARc4AJQA+qRUKmAIJEUgp73W1Q7KAlOAsYDuFMEJ5sKAAHnKAHomzAjjWPaYa7UCAATNsgGAEAAepbA-Z5sgANDA3m29AQjDAROAAOSwPQsQgwExIEwQAcwCuViAQZHlUgCIejgjjg+T2hT8qU69j3v3mS0NQKaWOZAsEx63qWJL1fW931nT8oDkRhgHIXhGB4GBLzsOgFGAkQUN4atawbDDLxnSk5AtDR-xMSkgNiF8RzfD9eWg2D4KYJDKVQ9CqKuSQYBwjg63rAiGg4hBiPKfgRAQADLyxXhQIAH1qQgEBIvgwHEgDKWkpUEBgeSYkU5SAG1QwAXV4X8KhbGB-wEdwnlCecWFgIYl2PGs3H7XcQBMGAMAPNSXj7I8DxYGBQlwo82AgEAjQ0Az0C8nyohsowLGQgRTLSuK92MsC70g+iYAAfm4mteIbS8sq84zCKuBReD0kplP1FZfVUIEwBBcBZwEfirxo8C6OnLTcogh8GLNJCsJK3Depq4T7D+E0FrGXwwhgIyQ2MrFltlJazni4cjEYGKDgmp8ZVlRQYFWqAEMYKAWAQG6ZIEGdPJMXhktSlCRQUEABEYRAUHQLAcHwYgyCg+7Hpugz3uM7E7FcI8wCXK1ZRgJGDyuyrvJ2GAvssS8Moq+KTBy-q8rGorprK3rceqrS6oUxqYGak79XOsYyMozC+pvKmoJguCEMmgQ2MEvmuJ4vj8NepmdohJb-sYpFsWUxxOZOsiKIw6iBdGoXtvMpWwkmedfKClSXTEK2ZfK+W9dqjXLS5ioxIkkwpKlE7xjNmoJpdjQtYujQPfUq5jd9zRbUVB0biITBArRFT-KHK5ZyiUCJsLMYQ81lq-Ta+FQSiEoEF6kbBsfYbKcN+iYIm3hWOw0q+IEojFdC-2ohNpwbvWkyo9Dpw9rqA6juj4IzqnpwruephoaesIXrevdPsXb7xd+i2ICxlG0dn-ecbJxKCc3on0pehmq-y2Qaft+mycZzv6oIfS2aDtm3eCHm5dvsaMFpZtzwiBeWnc+5jCWkcS2yNnTCFtsjR+cs5oiRHvnCoOsri831rRO+tBhZMUQs3TiO8nYIGATNAS5CfyzyWuXVWE11Ycx-k4cOXsAY+xHocY4Cozjx2pInDAydBzsO6pnYaOc9TBAwcEdhGkuEYyWoHFh5R2aqFap6dwABZIwFBKDhiuuQR6Zl4B9F4BiHYvg2afyLgGaA3UACM4Z-r1l0fouabwYJGJMUoMxIBeAeBwKue8AgPA2P1L8exsAyJmF7KBBg-1HEbWMsiYxCAUnRj6MiMijBtpoU8MEg8dJ-rhORpYvwvx-TgCigMZEbAQApFDGfWJfYUIrHsf0FEDSUitPiQIZE7DvYilFIUqAIToggFgKUqpqhonp2uM4hg5c3F6KoJ44SjB+HKhuAuCUg4VwIDYL2H4USakxIAnE+O4Zy5vEcTkiOLR8kKG2VSDwUyFzaUOcc05mT0krGqZALp9TGmMGafjPp8czlArqT0xgkKdmDNUp7BROS3IsFWiMhg7z2xfLeD834QA">this Typescript playground</a>.</p>
<pre><code class="language-typescript">function ok&lt;Ok extends object, Err extends object&gt;(value: Ok): Result&lt;Ok, Err&gt; {
  return {
    get [&#x10CF1;]() {
      return new Proxy(
        value,
        { get: (target, prop) =&gt; ok(target[prop]) }
      );
    },
    map&lt;NewOk extends object&gt;(cb: (e: Ok) =&gt; NewOk): Result&lt;NewOk, Err&gt; {
      return ok(cb(value));
    },
    // &#x2026;
  };
}
</code></pre>
<p>This is fairly straightforward: the function returns an object with some functions on it. The complicated bit is the <code>get [&#x10CF1;]()</code> definition here. There&apos;s some special stuff going, so let&apos;s break it down into the two maybe complicated bits:</p>
<ol>
<li>We define a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get">getter function</a> for the spider prop (using computed property names):</li>
</ol>
<pre><code class="language-typescript">get [&#x10CF1;]() {}
</code></pre>
<ol start="2">
<li>We create a Proxy on the actual value, which changes the property getter to wrap the result in a new call to <code>ok(&#x2026;)</code>:</li>
</ol>
<pre><code class="language-typescript">return new Proxy(value, { get: (target, prop) =&gt; ok(target[prop]) })
</code></pre>
<ol start="3">
<li>A bunch of type magic that I&apos;ve omitted to get this to work.</li>
</ol>
<p>This way, any time we try to access a property of this value, the proxy intercepts that and returns a resultified version of the value instead.</p>
<h1 id="whats-wrong-in-this-picture">What&apos;s wrong in this picture?</h1>
<p>Lots!</p>
<p>First of all, this whole thing is silly and bad. I&apos;m doing a lot of work here to build some weird silly syntax sugar that isn&apos;t even very nice to use.</p>
<p>Second, as I alluded to before, there&apos;s a lot of &#x2728; type magic &#x2728; that I&apos;m leaving out (including some naughty <code>any</code>s here and there).</p>
<p>Most importantly because <code>Proxy</code> can only work on objects, the Result has to operate on objects for now. That means it cannot work on primitive values, like strings and numbers. You still want to be able to call the spider operator on a string though, since you might want to call something like <code>.toUpperCase()</code> on it. In the (more) real version on the playground I actually call a function to convert primitive values into their object equivalents (like <code>String</code> and <code>Number</code>).</p>
<h1 id="are-there-next-steps">Are there next steps?</h1>
<p>This was an experiment! I&apos;m not intending to make something useful out of this. But who knows, maybe I&apos;ll continue experimenting!</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>This is extremely experimental, the code is quite silly and probably really brittle, and I don&apos;t necessarily like it myself. Use at your own peril. <a href="#fnref1" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
<li id="fn2" class="footnote-item"><p>I <a href="https://mastodon.nl/@martijnarts/110893053027520730">tooted</a>/<a href="https://twitter.com/martijnawts/status/1691389666581266432">tweeted</a> about this at some point. <code>&#x10CF1;</code> is a unicode character that&apos;s <a href="https://mothereff.in/js-variables#%F0%90%B3%B1">in the right block</a> to be used as a Javascript variable. I&apos;m using this because it&apos;s an experiment and I can do what I want to. <a href="#fnref2" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
<li id="fn3" class="footnote-item"><p>Yes, certainly we could just not use a symbol since it&apos;s a valid property name also, and we could just name the property &#x10CF1;, and access it like <code>ok1.&#x10CF1;.bar.&#x10CF1;.foo.map</code>. I simply think the way I&apos;m doing it looks a bit more like it&apos;s actual syntax. <a href="#fnref3" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title><![CDATA[Hosting quickly 1: Setting up Terraform, Github, and 1Password]]></title><description><![CDATA[<p>This is the first article in a series called &quot;Hosting quickly&quot;. I want to launch Rust apps quickly and often, and I want to have stable, common infrastructure for all of them.</p>
<ul>
<li><a href="https://blog.martijnarts.com/p/05e01f1d-56f7-4d18-9225-7f1d08962b7a/">Hosting quickly 1: Setting up Terraform, Github, and 1Password</a></li>
</ul>
<p>We&apos;ll call this Cochrane<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p>]]></description><link>https://blog.martijnarts.com/hosting-quickly-1-setting-up-terraform-github-and-1password/</link><guid isPermaLink="false">652950d385e3d400014dc493</guid><dc:creator><![CDATA[Martijn Arts]]></dc:creator><pubDate>Fri, 13 Oct 2023 14:28:49 GMT</pubDate><media:content url="https://blog.martijnarts.com/content/images/2023/10/latest-cochrane-shuttle.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.martijnarts.com/content/images/2023/10/latest-cochrane-shuttle.jpeg" alt="Hosting quickly 1: Setting up Terraform, Github, and 1Password"><p>This is the first article in a series called &quot;Hosting quickly&quot;. I want to launch Rust apps quickly and often, and I want to have stable, common infrastructure for all of them.</p>
<ul>
<li><a href="https://blog.martijnarts.com/p/05e01f1d-56f7-4d18-9225-7f1d08962b7a/">Hosting quickly 1: Setting up Terraform, Github, and 1Password</a></li>
</ul>
<p>We&apos;ll call this Cochrane<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>. The idea is simple enough: create a template repository for quickly booting up a new Rust web project. It&apos;ll include the server, database, queues and background workers, blob store and image serving, and metrics and alerts. My first pass will all rely on SaaS companies with free tiers to minimise my ops load. However, all the technologies are open source and I intend to write an alternative Terraform setup for hosting on any k8s instance.</p>
<p>Feel free to <a href="https://github.com/martijnarts/cochrane">skip to the repo</a>.</p>
<p>Here&apos;s the plan for this post:</p>
<ul>
<li><a href="#run-everything-from-one-repository">Run everything from one repository</a></li>
<li><a href="#manage-infrastructure-using-terraform">Manage infrastructure using Terraform</a></li>
<li><a href="store-terraform-secrets-and-state-in-a-1password-vault">Store Terraform secrets and secrets in a 1Password vault</a></li>
<li><a href="#manage-terraform-from-github">Manage Terraform from Github</a></li>
</ul>
<h1 id="run-everything-from-one-repository">Run everything from one repository</h1>
<p>First, we&apos;ll set up a Git monorepo managed through <a href="https://just.systems/">Just</a>. For this first post, we&apos;ll only require a few files and directories:</p>
<pre><code>.
&#x251C;&#x2500;&#x2500; .git/...
&#x251C;&#x2500;&#x2500; .github/
&#x2502;&#xA0;&#xA0; &#x2514;&#x2500;&#x2500; workflows/
&#x2502;&#xA0;&#xA0;     &#x2514;&#x2500;&#x2500; infra.yaml
&#x251C;&#x2500;&#x2500; infra/
&#x2502;&#xA0;&#xA0; &#x251C;&#x2500;&#x2500; justfile
&#x2502;&#xA0;&#xA0; &#x251C;&#x2500;&#x2500; main.tf
&#x2502;&#xA0;&#xA0; &#x2514;&#x2500;&#x2500; terraform.tfvars
&#x2514;&#x2500;&#x2500; justfile
</code></pre>
<p>In future posts, we&apos;ll add things like <code>server/</code>, <code>web/</code>, and <code>worker/</code>, a <code>Cargo.toml</code>, and presumably many other important files and projects.</p>
<h1 id="manage-infrastructure-using-terraform">Manage infrastructure using Terraform</h1>
<p>First, let&apos;s add the Github repository itself through Terraform. While this repository is intended as a template<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup>, it&apos;s also going to be an example and testing repo.</p>
<p>To do this, I have to <a href="https://github.com/settings/personal-access-tokens/new">register a new Github personal access token</a>. I&apos;ll call it cochrane. For now, I&apos;ll give this token full access to everything on the cochrane repository. Let&apos;s put that in a <code>terraform.tfvars</code> file (make sure to add it to the Gitignore!) and call it <code>github_token</code>. We&apos;ll also add a <code>github_owner</code> for and <code>github_name</code>.</p>
<p>Then, we add the repository config to our <code>main.tf</code>:</p>
<pre><code class="language-terraform">variable &quot;github_token&quot; {
  type = string
  description = &quot;Your Github OAuth token&quot;
}

variable &quot;github_owner&quot; {
  type = string
  description = &quot;The owner of the Github repository&quot;
}

variable &quot;github_name&quot; {
  type = string
  description = &quot;The name of the Github repository&quot;
}

terraform {
  required_providers {
    github = {
      source  = &quot;integrations/github&quot;
      version = &quot;~&gt; 5.0&quot;
    }
  }
}

provider &quot;github&quot; {
  token = var.github_token
  owner = var.github_owner
}

resource &quot;github_repository&quot; &quot;repository&quot; {
  name = var.github_name
  visibility = &quot;private&quot;
}
</code></pre>
<p>We&apos;ll separate this and clean it up when it starts risking getting messy. We&apos;ll need to setup the Terraform state:</p>
<pre><code class="language-bash">$ terraform init
$ terraform import github_repository.repository cochrane
</code></pre>
<p>And then for future usage we&apos;ll add the required commands to our <code>infra/justfile</code>, so that we can run things like <code>just infra/plan</code>.</p>
<pre><code class="language-justfile">plan:
    terraform plan -var-file=terraform.tfvars

apply:
    terraform apply -var-file=terraform.tfvars
</code></pre>
<h1 id="store-terraform-secrets-and-state-in-a-1password-vault">Store Terraform secrets and state in a 1Password vault</h1>
<p>This might be a bit unorthodox, but I don&apos;t like using more than one tool to do the same thing, and this entire project is about building something that works for me. I&apos;ll be storing the <code>terraform.tfvars</code> values in 1Password and using its <code>op inject</code> tool to load it. Additionally, we&apos;ll be using <code>op item</code> and <code>op read -o</code> to load the <code>terraform.tfstate</code> file.</p>
<h2 id="secrets">Secrets</h2>
<p>Let&apos;s start with the easy part: secrets. This is obviously a quick <code>op inject</code> away. First, I&apos;ve moved the Github token into a new Cochrane vault. Then we&apos;ll create a <code>terraform.tfvars.tpl</code> file with our 1Password secrets referenced:</p>
<pre><code class="language-tfvars">github_owner = &quot;martijnarts&quot;
github_name = &quot;cochrane&quot;
github_token = &quot;op://cochrane/github/token&quot;
</code></pre>
<p>I <em>could</em> now run <code>op inject</code> directly, but I don&apos;t want to worry about running this every time I update my secrets. Instead, let&apos;s modify the <code>justfile</code><sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup>!</p>
<pre><code class="language-justfile">inject-tfvars:
    op inject -i ./terraform.tfvars.tpl -o terraform.tfvars

plan: inject-tfvars
    terraform plan -var-file=terraform.tfvars

apply: inject-tfvars
    terraform apply -var-file=terraform.tfvars
</code></pre>
<p>Now Just will automatically inject the tfvars file whenever we try to plan or apply our changes.</p>
<h2 id="state">State</h2>
<p>Now for the other part: we want to store our Terraform state inside 1Password also. We&apos;ll reuse the Cochrane vault and create a new item called <code>terraform_state</code> with the current <code>terraform.tfstate</code> file in it:</p>
<pre><code class="language-bash">$ op document create infra/terraform.tfstate --vault cochrane --title &quot;terraform_tfstate&quot;
</code></pre>
<p>Now we just need to update the Justfile again:</p>
<pre><code class="language-justfile">inject-tfvars:
    op inject -i ./terraform.tfvars.tpl -o terraform.tfvars

load-tfstate:
    op document get --vault cochrane -o ./terraform.tfstate

store-tfstate:
    op document edit --vault cochrane terraform_tfstate ./terraform.tfstate

plan: inject-tfvars load-tfstate
    terraform plan -var-file=terraform.tfvars

apply: inject-tfvars load-tfstate &amp;&amp; store-tfstate
    terraform apply -var-file=terraform.tfvars
</code></pre>
<p>This works really nicely, especially Just&apos;s <code>&amp;&amp;</code> syntax for running other tasks before and after any given task.</p>
<h1 id="manage-terraform-from-github">Manage Terraform from Github</h1>
<h2 id="access-1password-from-github-actions">Access 1Password from Github Actions</h2>
<p>Now let&apos;s try running at least <code>just infra/plan</code> in Github Actions. We&apos;ll use a Service Account, so let&apos;s <a href="https://my.1password.com/developer-tools/infrastructure-secrets/serviceaccount/?type=github-actions">create that first</a>, store it in the vault and add it to the <code>terraform.tfvars.tpl</code> so we can have Terraform store the secret in the Github repository.</p>
<pre><code class="language-tfvars">gha_1p_service_account = &quot;op://cochrane/gha_1p_service_account/token&quot;
</code></pre>
<pre><code class="language-terraform">resource &quot;github_actions_secret&quot; &quot;op_service_account&quot; {
  repository = var.github_name

  secret_name = &quot;OP_SERVICE_ACCOUNT&quot;
  plaintext_value = var.gha_op_service_account
}

resource &quot;github_actions_secret&quot; &quot;op_service_account&quot; {
  repository = var.github_name

  secret_name = &quot;OP_SERVICE_ACCOUNT&quot;
  plaintext_value = var.gha_op_service_account
}
</code></pre>
<p>Running <code>just infra/apply</code> will store the secret appropriately!</p>
<h1 id="planning-in-actions">Planning in Actions</h1>
<p>I&apos;ll leave a lot of this as an exercise to the reader, cause there&apos;s a lot of code. It&apos;s not very complex though. First, we&apos;ll need an additional just command to save a plan to a file that we&apos;ll read out for a summary:</p>
<pre><code class="language-justfile">save-plan filename=&quot;tfplan&quot;: init-terraform inject-tfvars load-tfstate
    terraform plan -var-file=terraform.tfvars -out={{filename}}
</code></pre>
<p>Don&apos;t you love how simple that turns out with Just?</p>
<pre><code class="language-yaml">name: Apply infra changes
permissions:
  issues: write
on:
  pull_request:
    paths:
      - &quot;infra/**&quot;
      - &quot;.github/workflows/infra.yaml&quot;

jobs:
  plan:
    runs-on: ubuntu-latest
    steps:
      # omitting:
      # - actions/checkout
      # - 1password/install-cli-action
      # - extractions/setup-just
      # - hashicorp/setup-terraform
      # - kishaningithub/setup-tf-summarize

      - name: Run Terraform Plan
        env:
          OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT }}
        run: just --dotenv-filename .just-env infra/save-plan

      - name: summary in table format
        working-directory: ./infra
        run: |
          rm -rf tf-summarize-table-output.md
          echo &quot;## tf-summarize output:&quot; &gt; tf-summarize-table-output.md
          echo &quot;&quot; &gt;&gt; tf-summarize-table-output.md
          terraform show -json tfplan | tf-summarize -md &gt;&gt; tf-summarize-table-output.md

      - name: Find Comment
        uses: peter-evans/find-comment@v2
        id: fc
        with:
          issue-number: ${{ github.event.pull_request.number }}
          comment-author: &quot;github-actions[bot]&quot;
          body-includes: tf-summarize output

      - name: Create comment with terraform plan summary
        uses: peter-evans/create-or-update-comment@v2
        with:
          comment-id: ${{ steps.fc.outputs.comment-id }}
          issue-number: ${{ github.event.pull_request.number }}
          body-file: &quot;./infra/tf-summarize-table-output.md&quot;
          edit-mode: replace

</code></pre>
<p>This works really nicely, giving a brief summary of the full plan in a PR comment, and updating the summary whenever you commit a change.</p>
<p>Theoretically, we might also want to <em>apply</em> with Github Actions. However, I think that&apos;s still a bit too risky for now. Maybe when I&apos;ve put some more thought into it.</p>
<h1 id="stay-tuned-for-the-application">Stay tuned for the application</h1>
<p>Now that we have the infrastructure set up, we&apos;ll want to run a backend and frontend. Next time, we&apos;ll set up a really simple Dioxus frontend and Axum backend and deploy them to Fly.io.</p>
<p>You can <a href="https://github.com/martijnarts/cochrane">find Cochrane here</a>.</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>This is in reference to <a href="https://memory-alpha.fandom.com/wiki/Cochrane_(2372)">the Cochrane 04 shuttle from Voyager</a> that broke the transwarp barrier in an altogether absurd episode. <a href="#fnref1" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
<li id="fn2" class="footnote-item"><p>In the actual repository, I&apos;ve also added a <code>setup</code> step to the justfile which actually takes you through the creation of the repo and initialisation and importing of the repository. I&apos;m not putting that here, but you can go check it out if you want. It does mean that I have some code in here that might not make sense when setting up for one specific repository. <a href="#fnref2" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
<li id="fn3" class="footnote-item"><p>Now ideally, we would have this work like Make, where <code>inject-tfvars</code> only gets run when the <code>.tpl</code> file actually got changed. There&apos;s <a href="https://github.com/casey/just/issues/867">an open issue</a> for that on Just, however it doesn&apos;t seem to be prioritized currently. It&apos;s fast enough anyway &#x1F937;&#x1F3FB;&#x200D;&#x2642;&#xFE0F; <a href="#fnref3" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title><![CDATA[Predictable programming 3 - using XState]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>In the last two posts (<a href="https://blog.martijnarts.com/predictable-programming-1-how-typescript-isnt-rust/">1</a>, <a href="https://blog.martijnarts.com/predictable-programming-2-making-typescript-more-like-rust/">2</a>) in this series, I explained why I want Typescript to be more predictable, and some of the most important guidelines I follow to make that happen. Here, I want to dive into a specific library that has helped me significantly improve the predictability</p>]]></description><link>https://blog.martijnarts.com/predictable-programming-3-using-xstate/</link><guid isPermaLink="false">64f9a10b77cef90001927724</guid><dc:creator><![CDATA[Martijn Arts]]></dc:creator><pubDate>Thu, 07 Sep 2023 10:15:26 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>In the last two posts (<a href="https://blog.martijnarts.com/predictable-programming-1-how-typescript-isnt-rust/">1</a>, <a href="https://blog.martijnarts.com/predictable-programming-2-making-typescript-more-like-rust/">2</a>) in this series, I explained why I want Typescript to be more predictable, and some of the most important guidelines I follow to make that happen. Here, I want to dive into a specific library that has helped me significantly improve the predictability of my React components&apos; behaviour: <a href="https://xstate.js.org/">XState</a>.</p>
<h1 id="why-state-machines">Why state machines?</h1>
<p>There&apos;s a pretty common issue that I&apos;ve seen with React components, where the usage of many different <code>useEffect</code>, <code>useCallback</code>, <code>useState</code>, etc. combined makes for a very unpredictable component: it&apos;s very much not obvious what should happen as a result of pressing any button, or any inputs changing. This makes React components very hard to test.</p>
<p>Now one way to resolve this is to be very strict with yourself about decomposing the behavior into separate, bespoke hooks that you can individually test. However, this adds a lot of cognitive overhead and often many refactors.</p>
<p>A state machine can clarify this process by restricting both the states that a component can be in, as well as the ways that it can get to that state. This makes it easier to develop, as there is less cognitive overhead of keeping in mind all the possible hooks that could affect your changes. Instead, you simple modify the state graph at the relevant part and you don&apos;t worry about the rest of the states.</p>
<h1 id="xstate-101">XState 101</h1>
<p>A state machine<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> is a simple chart of different states that the machine can be in, and the transitions between those states. Complexer machines will have &quot;submachines&quot; within states. It&apos;s pretty straightforward, and <a href="https://stately.ai/docs/states/intro">the XState docs</a> do a better job of explaining it.</p>
<p>Here&apos;s a simple example XState machine:</p>
<pre><code class="language-typescript">const machine = createMachine({
  tsTypes: {},
  schema: {} as {
  },
  id: &apos;trafficLight&apos;,
  initial: &apos;green&apos;,
  states: {
    green: {},
  },
});
</code></pre>
<h1 id="predictable-xstate">Predictable XState?</h1>
<p>While state machines by their very nature are more predictable than the spaghetti-logic of hooks, there are many ways to make them even stricter. It&apos;s important that we make it hard for ourselves to mess up our apps.</p>
<p>The first thing is to strongly type your XState definitions. Using Typescript with XState&apos;s typegen is not perfect, but it does a lot to ensure your machines adhere to a certain basic set of rules.</p>
<p>State machines manage to make even complex logic look a lot simpler. It&apos;s still important to break up your machines into smaller machines. You&apos;ll find yourself building a lot of smaller substates that can split out. I recommend to write your full state graph in one go, but take a moment after to refactor out the submachines.</p>
<p>Rendering out your state machine and running through it in Stately&apos;s <a href="https://stately.ai/registry/new?source=landing-page&amp;mode=Simulate">simulator</a> is also an excellent way of testing your machine has the right states and transitions.</p>
<h1 id="testing-machines">Testing machines</h1>
<p>It&apos;s important to test your machines. To ensure testable machines, I&apos;ve resorted to injecting external effects through the machine&apos;s context. This way, I can test the machine in isolation by providing mocks in the context. Looks a bit like this:</p>
<pre><code class="language-typescript">const fetchMachine = createMachine({
  id: &apos;fetch&apos;,
  initial: &apos;idle&apos;,
  context: {
    fetchService: null
  },
  states: {
    idle: {
      on: {
        FETCH: &apos;loading&apos;
      }
    },
    loading: {
      invoke: {
        src: (context) =&gt; context.fetchService(),
        onDone: {
          target: &apos;success&apos;
        }
      }
    },
    success: {
      type: &apos;final&apos;
    }
  }
});
</code></pre>
<pre><code class="language-typescript">test(&apos;fetch machine should reach &quot;success&quot; state&apos;, done =&gt; {
  // Mock API service
  const mockFetchService = () =&gt; Promise.resolve();
  
  // Create a service using the machine with the mock fetch service
  const service = interpret(fetchMachine.withContext({
    fetchService: mockFetchService
  }));
  
  // Listen to state transitions
  service.onTransition(state =&gt; {
    if (state.matches(&apos;success&apos;)) {
      done();
    }
  });

  // Start the service and send the FETCH event
  service.start();
  service.send(&apos;FETCH&apos;);
});
</code></pre>
<h1 id="beyond-xstate">Beyond XState</h1>
<p>XState v5 is also coming soon and promises to bring a bunch of improvements. I&apos;m excited to try out the new stuff.</p>
<p>Additionally, I feel like there&apos;s a possibility of a mostly Typescript state graph library. XState feels very heavy to use still, especially when using its type generation and all its various features. I believe there&apos;s a possibility to simplify that and to rely mostly on Typescript for enforcing the state machine properties.</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>Actually a state graph. <a href="#fnref1" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
</ol>
</section>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Easier generic functions over similar structs in Rust]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>While developing <a href="https://rckive.com">RCKIVE</a>, I&apos;ve done a development against Shopify&apos;s GraphQL APIs. I quickly found out that the dealing with GraphQL in Rust could be challenging. Generating Rust serde structs and clients from GraphQL is seamless, but GraphQL&apos;s lenient response typing doesn&apos;t combine</p>]]></description><link>https://blog.martijnarts.com/easier-generic-functions-over-similar-structs-in-rust/</link><guid isPermaLink="false">64b7cbf1a6546b00019b9184</guid><dc:creator><![CDATA[Martijn Arts]]></dc:creator><pubDate>Wed, 19 Jul 2023 16:52:17 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>While developing <a href="https://rckive.com">RCKIVE</a>, I&apos;ve done a development against Shopify&apos;s GraphQL APIs. I quickly found out that the dealing with GraphQL in Rust could be challenging. Generating Rust serde structs and clients from GraphQL is seamless, but GraphQL&apos;s lenient response typing doesn&apos;t combine well with Rust&apos;s strict typing. Let&apos;s illustrate the problem using <a href="https://github.com/graphql-rust/graphql-client">graphql-client</a> and Shopify&apos;s API:</p>
<pre><code class="language-graphql">query GetOrders {
  orders(first: 10) {
    edges {
      node {
        id
        displayFulfillmentStatus
        lineItems(first: 10) {
          edges {
            node {
              id
              customAttributes {
                key
                value
              }
            }
          }
        }
      }
    }
  }
}
</code></pre>
<p>With the following Rust code:</p>
<pre><code class="language-rust">#[derive(GraphQLQuery)]
#[graphql(
    schema_path = &quot;schema.graphql&quot;,
    query_path = &quot;orders_query.graphql&quot;,
)]
pub struct OrdersQuery;
</code></pre>
<p>That <code>OrdersQuery</code> struct will expand to something like this (heavily edited for readability):</p>
<pre><code class="language-rust">struct GetOrders;
mod get_orders {
    type ID = String;
    pub enum OrderDisplayFulfillmentStatus { ... }

    pub struct ResponseData {
        pub orders: GetOrdersOrders,
    }
    pub struct GetOrdersOrders {
        pub edges: Vec&lt;GetOrdersOrdersEdges&gt;,
    }
    pub struct GetOrdersOrdersEdges {
        pub node: GetOrdersOrdersEdgesNode,
    }
    pub struct GetOrdersOrdersEdgesNode {
        pub id: ID,
        #[serde(rename = &quot;displayFulfillmentStatus&quot;)]
        pub display_fulfillment_status: OrderDisplayFulfillmentStatus,
        #[serde(rename = &quot;lineItems&quot;)]
        pub line_items: GetOrdersOrdersEdgesNodeLineItems,
    }
    pub struct GetOrdersOrdersEdgesNodeLineItems {
        pub edges: Vec&lt;GetOrdersOrdersEdgesNodeLineItemsEdges&gt;,
    }
    pub struct GetOrdersOrdersEdgesNodeLineItemsEdges {
        pub node: GetOrdersOrdersEdgesNodeLineItemsEdgesNode,
    }
    pub struct GetOrdersOrdersEdgesNodeLineItemsEdgesNode {
        pub id: ID,
        #[serde(rename = &quot;customAttributes&quot;)]
        pub custom_attributes: Vec&lt;
            GetOrdersOrdersEdgesNodeLineItemsEdgesNodeCustomAttributes,
        &gt;,
    }
    pub struct GetOrdersOrdersEdgesNodeLineItemsEdgesNodeCustomAttributes {
        pub key: String,
        pub value: Option&lt;String&gt;,
    }
}
</code></pre>
<h1 id="mapping-all-the-way-down">Mapping all the way down</h1>
<p>To get an attribute from a line item, the resulting queries become deeply nested:</p>
<pre><code class="language-rust">let orders = data.orders.edges;

for order in orders {
    let value = order
        .node
        .line_items
        .edges
        .iter()
        .flat_map(|line_item| &amp;line_item.node.custom_attributes)
        .find_map(|attr| match attr.key.as_str() {
            &quot;custom_attr&quot; =&gt; attr.value.clone(),
            _ =&gt; None,
        });
}
</code></pre>
<p>Now imagine that you want to write a generic function that could work across multiple, similar queries. In Typescript I could easily type the function so that I accept any object that looks has the data I need. This is strict duck typing:</p>
<pre><code class="language-typescript">type EdgeNodes&lt;T&gt; = { edges: Array&lt;{ node: T }&gt; };

type HandleXArg = {
    orders: EdgeNodes&lt;{
        line_items: EdgeNodes&lt;{
            custom_attributes: Array&lt;{
                key: string,
                value?: string,
            }&gt;
        }&gt;
    }&gt;
};

function handleX(arg: HandleXArg) {
    // ...
}
</code></pre>
<p>However, Rust structs are nominatively typed. I cannot tell the type system to only need specific parts. I could use GraphQL&apos;s Fragments, but that would still require me to rewrite my queries if I want to use it in different functions.</p>
<h1 id="substruct"><code>substruct</code></h1>
<p>And so I&apos;m building <a href="https://github.com/martijnarts/substruct">substruct</a>. Let&apos;s see some example code first:</p>
<pre><code class="language-rust">#[derive(substructRoot)]
struct User {
    id: String,
    name: String,
    created_at: DateTime&lt;Utc&gt;,
}

#[substruct_child(
    root = User,
    fields(id, name, created_at),
)]
struct FunctionA;

#[substruct_child(
    root = User,
    fields(id, created_at),
)]
struct FunctionB;

#[substruct_use(
    root = User,
    fields(id, created_at),
)]
fn get_name(query: _) {
    println!(&quot;{}: {:?}&quot;, query.id(), query.created_at())
}
</code></pre>
<p>The <code>get_name</code> function now looks almost duck typed!</p>
<h1 id="under-the-hood">Under the hood</h1>
<p>In the background, we create a getter trait for each field of the root struct. Each child struct then gets implementations for the fields it inherits. Let&apos;s look at that first<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>. We&apos;ll start with the root struct:</p>
<pre><code class="language-rust">struct User {
    id: String,
    name: String,
    created_at: DateTime&lt;Utc&gt;,
}

// Getter trait for the `id` field ...
trait __User__Id {
    fn id(&amp;self) -&gt; &amp;String;
}
// ... and its implementation.
impl __User__Id for User {
    fn id(&amp;self) -&gt; &amp;String {
        &amp;self.id
    }
}
// We also create a type alias, we&apos;ll get to that later.
type __User__Id__Type = String;

// `name` and `created_at` field traits hidden for brevity.
</code></pre>
<p>The child structs reference the field types<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup> and implement the traits. For <code>FunctionA</code>, that looks like this:</p>
<pre><code class="language-typescript">// We generate the full struct, referencing the root&apos;s field types.
struct FunctionA {
    id: __User__Id__Type,
    name: __User__Name__Type,
    created_at: __User__CreatedAt__Type,
}

// And we implement the getter traits for each field.
impl __User__Id for FunctionA {
    fn id(&amp;self) -&gt; &amp;__User__Id__Type {
        &amp;self.id
    }
}
// Again, imagine the same for `name` and `created_at`...
</code></pre>
<p>When modifying a function, substruct then creates a special trait that inherits the getter traits for the required fields. It also immediately implements this trait generically for any type that implements all the getter traits<sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup>.</p>
<pre><code class="language-typescript">trait GetNameInput: __User__Id + __User__CreatedAt {}
impl&lt;T: __User__Id + __User__CreatedAt&gt; GetNameInput for T {}
fn get_name(query: impl GetNameInput) {
    // ...
}
</code></pre>
<h1 id="whats-next">What&apos;s next?</h1>
<p>Next up would be validating my implementation by integrating it into a GraphQL library. I might fork <code>graphql-client</code> to make this work in there.</p>
<p>To get there, there is one major problem that needs solving. Substruct doesn&apos;t yet support the use case I started out with where I want the function to be generic over a deeply nested type. I wonder if leveraging Generic Associated Types (GATs) in Rust could be the solution. This is something I&apos;m eager to experiment with next!</p>
<p>I will add that I&apos;m doing this mostly as an exercise to learn proc macros. Don&apos;t expect this to be finished, unless you feel like contributing! However, right now I&apos;m excited to continue developing, and plan to contribute additional documentation to <a href="https://docs.rs/darling/latest/darling/">darling</a> (which has been very useful).</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>The expanded code isn&apos;t very streamlined yet, so keep in mind that this is a work in progress: <a href="#fnref1" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
<li id="fn2" class="footnote-item"><p>The macro cannot find the actual type directly since it only has access to the struct it&apos;s operating on. Instead, these type aliases are named in a particular way to allow their referencing with the information we have in the child structs. <a href="#fnref2" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
<li id="fn3" class="footnote-item"><p>Trait inheritance is unidirectional. From an old <a href="https://users.rust-lang.org/t/extending-traits/1802/2">Rust forum post</a>: &quot;However, this does not mean that if a type extends&#xA0;<code>B</code>&#xA0;it will automatically extend&#xA0;<code>A</code>; trait inheritance is just a way to specify requirements, that is,&#xA0;<code>trait B: A</code>&#xA0;means that we can know that if some type&#xA0;<code>T</code>&#xA0;implements&#xA0;<code>B</code>, it also necessarily implements&#xA0;<code>A</code>.&quot; However, since our new trait doesn&apos;t introduce any unique characteristics, the generic implementation can be empty, solely relying on the other traits. <a href="#fnref3" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
</ol>
</section>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Predictable programming 2: making Typescript more like Rust]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>This is the second post in a series of blog posts where I explore ways to make Typescript development more predictable. I describe my motivations in-depth in my first post. In this post, I&apos;ll focus on a list of concrete techniques to make Typescript development more predictable.</p>
<h1 id="optional-results">Optional</h1>]]></description><link>https://blog.martijnarts.com/predictable-programming-2-making-typescript-more-like-rust/</link><guid isPermaLink="false">6462097100a674000145fdeb</guid><dc:creator><![CDATA[Martijn Arts]]></dc:creator><pubDate>Wed, 07 Jun 2023 09:08:37 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>This is the second post in a series of blog posts where I explore ways to make Typescript development more predictable. I describe my motivations in-depth in my first post. In this post, I&apos;ll focus on a list of concrete techniques to make Typescript development more predictable.</p>
<h1 id="optional-results">Optional results</h1>
<p>Typescript by itself provides a lot of great protections already, but it&apos;s not enough to fully satisfy my requirements. The biggest change from regular Typescript development will be transitioning largely to <a href="https://github.com/gcanti/fp-ts">fp-ts</a> paradigms. Its <code>Option&lt;T&gt;</code> type and tooling are a great replacement for a lot of Rust&apos;s equivalent tools, and the <code>Either&lt;E, A&gt;</code> type is a good start for an equivalent of Rust&apos;s <code>Result&lt;A, E&gt;</code> type.</p>
<p>I don&apos;t believe these are necessarily complete solutions: we don&apos;t get the <code>?</code> operator ergonomics here, which means that a lot of function definitions start looking very different. It forces the user to use fp-ts&apos;s less intuitive <code>pipe</code>s and <code>task</code>s. There are many alternatives that I&apos;ll be looking at, like <a href="https://github.com/supermacro/neverthrow">neverthrow</a>, <a href="https://github.com/mobily/ts-belt">ts-belt</a>, <a href="https://github.com/swan-io/boxed">boxed</a>, <a href="https://github.com/true-myth/true-myth">true-myth</a> and others. Maybe one of these has better ergonomics.</p>
<p>However, I don&apos;t see how anything can add a <code>?</code> operator. The most signficant improvement we may get is the <a href="https://github.com/tc39/proposal-pipeline-operator">pipeline operator</a>, which could replace calls to <code>pipe</code> with a more readable syntax.</p>
<h1 id="read-or-write">Read or write?</h1>
<p>A commonly hailed paradigm even in current frontend development is immutability. To minimize side-effects, it&apos;s become common practice to avoid ever modifying objects. In Rust, this is actually enforced by the type system through <code>mut</code> variables: everything&apos;s immutable by default. While we probably can&apos;t reproduce that in Typescript, we can get quite far by just making as many things immutable as possible.</p>
<p>Typescript comes with the <code>readonly</code> keyword and some built-in readonly types that we can use on our function definitions. For types coming from outside of our system we can use <a href="https://github.com/sindresorhus/type-fest">type-fest</a>&apos;s <code>ReadonlyDeep</code><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> to restrict it.</p>
<p>However, this does not enforce &quot;immutable by default&quot;, and it&apos;s quite difficult to do so. There are <a href="https://github.com/microsoft/TypeScript/issues/32758">some</a> <a href="https://github.com/microsoft/TypeScript/issues/42357">open</a> <a href="https://github.com/microsoft/TypeScript/issues/21152">issues</a> on Typescript&apos;s Github (and a <a href="https://github.com/tc39/proposal-record-tuple">TC39 proposal</a>) that would make this a lot easier, but for now we&apos;ll have to rely on linting. For that, I recommend the <a href="https://github.com/eslint-functional/eslint-plugin-functional/blob/next/docs/rules/prefer-immutable-types.md"><code>functional/prefer-immutable-types</code></a><sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup> and explicitly ignoring any exceptions.</p>
<h1 id="ergonomics">Ergonomics</h1>
<p>One of the big gamechangers of my Typescript work has been <a href="http://github.com/gvergnaud/ts-pattern">ts-pattern</a>. It works almost the same as Rust&apos;s <code>match</code> statement, can do exhaustive matching on <em>every type</em>, extractions from patterns, etc. It&apos;s great.</p>
<p>Additionally errors in Javascript are awful to work with. But <a href="#TODO">there is a cure</a>, and combining <a href="https://github.com/martijnarts/ts-strict-error">ts-strict-error</a><sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup> with ts-pattern makes for a much nicer experience handling errors.</p>
<h1 id="guarding-the-gateways">Guarding the gateways</h1>
<p>Many of the errors I&apos;ve run into (both in Typescript and in other languages) stem from badly defined or badly followed API contracts. Nowadays, I make sure to always validate data at the boundary. Either I use a strictly validated codegen library, or I use something like <a href="https://github.com/gcanti/io-ts">io-ts</a> for defining and validating the data structure. I use this for APIs without a client library, but also sometimes for flaky external libraries. Just be strict about your typings, and make sure to make everything readonly<sup class="footnote-ref"><a href="#fn4" id="fnref4">[4]</a></sup>.</p>
<p>There are almost no Javascript libraries I&apos;ve seen that properly encode their errors. This makes it extremely difficult to work with the failure cases of external libraries. I am starting to build out a library of convertors for external libraries inside ts-strict-error, but it is so far incomplete and I very much welcome pull requests.</p>
<h1 id="linting">Linting</h1>
<!--
- eslint rules
    - explicit-function-return-type
    - prefer-readonly-parameter-types
    - disallow throw?
    - require wrapped external calls?
-->
<p>The last big component of making this work will be static analysis of our code. A lot of lints already exist that we can reuse. I&apos;ll list some lints and why I believe you should activate them:</p>
<ul>
<li><a href="https://github.com/eslint-functional/eslint-plugin-functional/blob/next/docs/rules/prefer-immutable-types.md"><code>functional/prefer-immutable-types</code></a>: as explained before, types should only be mutable when we need them to be mutable, and we should always be explicit about this where we use them.</li>
<li><a href="https://typescript-eslint.io/rules/explicit-function-return-type"><code>@typescript-eslint/explicit-function-return-type</code></a>: in Rust, all functions must have an explicit return type defined. Automatically inferring types is great, but being explicit at some points helps prevent type inferrence accidents across the board. I find functions to be a perfect boundary for this.</li>
<li><a href="https://github.com/eslint-functional/eslint-plugin-functional/blob/next/docs/rules/no-throw-statements.md"><code>functional/no-throw-statements</code></a>: errors should be returned as results (or <code>Either</code>, in fp-ts), never thrown.</li>
</ul>
<p>Remember that you can explicitly ignore eslint rules with <code>// eslint-disable-line rule-name -- comment</code> and use it whenever needed. I recommend using <a href="https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/require-description.html">eslint-comments/require-description</a> to ensure you&apos;re always explaining why.</p>
<p>One more thing I&apos;d like to lint for, but can&apos;t, is wrapping external calls in fp-ts&apos;s <code>either.tryCatch</code>, to map any errors from external libraries into StrictErrors. This would ensure that all errors are explicitly caught and handled at calling time. I might try to write a custom lint for this to try it out.</p>
<h1 id="whats-next">What&apos;s next?</h1>
<p>I&apos;m collecting these recommendations <a href="https://martijnarts.com/atoms/Predictable+Typescript+styleguide">in my digital garden</a> for now. While testing out this style guide of sorts, I will also be amending that page. If I ever get to a significantly nicer version, I&apos;ll probably write another blog post here.</p>
<p>Additionally, there&apos;s another blog post coming up in this series. One major improvement in my frontend development workflow has been state machines. Subscribe for email notifications or to the RSS feed to learn more about developing predictable frontends with XState.js!</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>Or you can use <a href="https://github.com/ts-essentials/ts-essentials">ts-essentials</a>&apos; <code>DeepReadonly</code>. <a href="#fnref1" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
<li id="fn2" class="footnote-item"><p>There&apos;s a set of typescript-eslint rules called <a href="https://typescript-eslint.io/rules/prefer-readonly"><code>prefer-readonly</code></a> and <a href="https://typescript-eslint.io/rules/prefer-readonly-parameter-types"><code>prefer-readonly-parameter-types</code></a> which would bring you <em>some</em> of the way, but they&apos;re not nearly as broad and advanced as <code>prefer-immutable-types</code>. The latter applies everywhere, not just in parameters and classes, and it actually does some smart type analysis to see if the types are actually immutable, and <a href="https://github.com/typescript-eslint/typescript-eslint/issues/4536#issue-1129449772">not just readonly</a>. <a href="#fnref2" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
<li id="fn3" class="footnote-item"><p>Disclaimer here: I created ts-strict-error. <a href="#fnref3" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
<li id="fn4" class="footnote-item"><p>io-ts has a <code>t.readonly(A)</code> type, but that doesn&apos;t provide deep readonly capabilities. However, it&apos;s probably a good exercise anyway to implement a <code>t.readonlyDeep(A)</code> that builds on <code>ts-fest</code> (and it&apos;s not too difficult). <a href="#fnref4" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
</ol>
</section>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Building a better Typescript error]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Javascript errors are bad. I think we can all agree that <code>new Error(&quot;...&quot;)</code> doesn&apos;t give you something super useful to work with. Extending the Error class is surprisingly <a href="https://stackoverflow.com/questions/1382107/whats-a-good-way-to-extend-error-in-javascript">difficult</a>. After that, actually dealing with the errors turns into a bit of a <a href="https://stackoverflow.com/a/51768316/238310">guessing game</a> on what</p>]]></description><link>https://blog.martijnarts.com/building-a-better-typescript-error/</link><guid isPermaLink="false">645e3e3062716e0001242e44</guid><dc:creator><![CDATA[Martijn Arts]]></dc:creator><pubDate>Wed, 17 May 2023 14:00:13 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Javascript errors are bad. I think we can all agree that <code>new Error(&quot;...&quot;)</code> doesn&apos;t give you something super useful to work with. Extending the Error class is surprisingly <a href="https://stackoverflow.com/questions/1382107/whats-a-good-way-to-extend-error-in-javascript">difficult</a>. After that, actually dealing with the errors turns into a bit of a <a href="https://stackoverflow.com/a/51768316/238310">guessing game</a> on what type of error actually happened.</p>
<h2 id="the-right-way">The right way</h2>
<p>I have one sort of &quot;north star&quot; here, which is the <code>thiserror</code> crate in Rust. It allows you to define errors as enums, with strongly typed metadata and even defining which errors can cause a specific variant. Here&apos;s a quick example, taken from their documentation:</p>
<pre><code class="language-rust">#[derive(Error, Debug)]
pub enum DataStoreError {
    Disconnect(#[from] io::Error),
    Redaction(String),
    InvalidHeader {
        expected: String,
        found: String,
    },
    Unknown,
}
</code></pre>
<p>This then allows you to use Rust&apos;s wonderful <code>match</code>-statement to parse the error later:</p>
<pre><code class="language-rust">match err {
    DataStoreError::Disconnect(cause) =&gt; ...,
    DataStoreError::Redaction(key) =&gt; ...,
    DataStoreError::InvalidHeader { expected, found } =&gt; ...,
    DataStoreError::Unknown =&gt; ...,
}
</code></pre>
<p>Since <code>match</code> requires exhaustive options, this prevents any errors from staying unhandled. It also makes it super easy for libraries to describe and pass any error case.</p>
<h2 id="so-what-do-i-want-in-typescript">So what do I want in Typescript?</h2>
<p>So we just saw that there are two parts to this: easily creating categories of connected errors, and matching on those errors. In the end, our error definitions will look something like this:</p>
<pre><code class="language-typescript">const Disconnect = createStrictError&lt;
  &quot;Disconnect&quot;,
  IoError
&gt;(&quot;Disconnect&quot;);
const Redaction = createStrictError&lt;&quot;Redaction&quot;, undefined, string&gt;(
  &quot;Redaction&quot;
);
const InvalidHeader = createStrictError&lt;
  &quot;InvalidHeader&quot;,
  undefined,
  { expected: string; found: string }
&gt;(&quot;InvalidHeader&quot;);
const Unknown = createStrictError&lt;&quot;Unknown&quot;, Error&gt;(&quot;Unknown&quot;);

const DataStoreError = createStrictError&lt;
  &quot;DataStoreError&quot;,
  | InstanceType&lt;typeof Disconnect&gt;
  | InstanceType&lt;typeof Redaction&gt;
  | InstanceType&lt;typeof InvalidHeader&gt;
  | InstanceType&lt;typeof Unknown&gt;
&gt;(&quot;DataStoreError&quot;);
</code></pre>
<p>It&apos;s not ideal<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>, but it&apos;s easy enough to define strong error types, including typed causes and contexts. Additionally, I want to make sure that it&apos;s exhaustively matchable using <a href="https://github.com/gvergnaud/ts-pattern">ts-pattern</a>.</p>
<pre><code class="language-typescript">match(err.cause)
  .with({ name: &quot;Disconnect&quot; }, ({ cause }) =&gt; (/*...*/))
  .with({ name: &quot;Redaction&quot; }, ({ context: key }) =&gt; (/*...*/))
  .with({ name: &quot;InvalidHeader&quot; }, ({ context: { expected, found } }) =&gt; (/*...*/))
  .with({ name: &quot;Unknown&quot; }, ({ cause }) =&gt; (/*...*/))
  .exhaustive();
</code></pre>
<h2 id="how-do-we-build-that">How do we build that?</h2>
<p>Our new error type needs to carry some type information about the potential causes and contexts, as well as a proper differentiable name property to be matched on. We&apos;ll use <a href="https://github.com/adriengibrat/ts-custom-error">ts-custom-error</a> to resolve the <code>extends Errors</code> issues we alluded to at the start. Our first step is to create a class that can do all these things. We&apos;ll create an <a href="https://www.typescriptlang.org/docs/handbook/2/classes.html#abstract-classes-and-members"><code>abstract class</code></a> to prevent using this error type directly.</p>
<pre><code class="language-typescript">import { CustomError } from &quot;ts-custom-error&quot;;

abstract class StrictError&lt;
  const Type extends string,
  const Cause extends Error | undefined = undefined,
  const Context = undefined
&gt; extends CustomError {
  // TODO
}
</code></pre>
<p>You&apos;ll note I&apos;ve marked all the type parameters as <code>const</code>, since we (probably?) never want any non-const types defined here. Aside from that it&apos;s all pretty basic types and classes. We&apos;ll give both <code>Cause</code> and <code>Context</code> an option of being <code>undefined</code>, which we&apos;ll assume means you don&apos;t want the error to use that.</p>
<p>Now, let&apos;s define our properties:</p>
<pre><code class="language-typescript">abstract class /* ... */ {
  abstract readonly name: Type;
  readonly cause: Cause;
  readonly context: Context;

  // TODO
}
</code></pre>
<p>I think this is pretty self-explanatory. They reference the generic types that the class gets instantiated with, and they&apos;re <code>readonly</code> because you should never be allowed to modify an error&apos;s properties.</p>
<p>The constructor should be pretty simple also:</p>
<pre><code class="language-typescript">class /* ... */ {
  /* ... */

  constructor(
    readonly message: string,
    readonly options: { cause: Cause, context: Context }
  ) {
    super(message, { cause: options.cause });

    this.cause = options.cause;
    this.context = options.context;
  }
}
</code></pre>
<p>Now we can create subclasses of this like so:</p>
<pre><code class="language-typescript">class Disconnect extends StrictError&lt;&quot;Disconnect&quot;, IoError&gt; {
  readonly name = &quot;Disconnect&quot;;
}

// and it works!
declare const y: IoError; 
const x = new Disconnect(&quot;We disconnected!&quot;, { cause: y, context: undefined });
</code></pre>
<p>This is still quite explicit. First of all, we need to use the full class syntax, and set the <code>name</code> property manually. Secondly, we can&apos;t just omit the <code>context</code> property here even though we know it has to be undefined anyway<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup>. Let&apos;s fix those one at a time.</p>
<h3 id="building-a-class-factory">Building a class factory</h3>
<p>To create these classes repeatedly, it&apos;ll be a lot easier if we build what&apos;s often called a &quot;factory&quot;, or a creator of classes. We&apos;ll build up a function. Please don&apos;t mind the weird (but valid) formatting here, I&apos;ve only done that to separate the components.</p>
<pre><code class="language-typescript">function createStrictError

  // 1. Type parameters
  &lt;
    const Type extends string,
    const Cause extends Error | undefined = undefined,
    const Context = undefined
  &gt;

  // 2. Function parameters
  (type: Type)

  // 3. Return type
  : new (
    ...params: ConstructorParameters&lt;typeof StrictError&lt;Type, Cause, Context&gt;&gt;
  ) =&gt; StrictError&lt;Type, Cause, Context&gt; {
  // TODO
}
</code></pre>
<p>To go through that step-by-step:</p>
<ol>
<li>We simply copy the type parameters from the abstract class, which we&apos;ll need to construct the <code>StrictError</code>.</li>
<li>We need to pass the <code>Type</code> as a value here. You can see this in the example, where we pass the name as both a type parameter <em>and</em> a function parameter for each new error definition.</li>
<li>The return type looks a bit daunting, but because of <a href="https://www.typescriptlang.org/docs/handbook/2/functions.html#construct-signatures">how classes work in Javascript</a> this is how you return a class in Typescript. You can see it says to return a function that:
<ul>
<li>can be called as a constructor (<code>new</code>);</li>
<li>takes the parameters of the <code>StrictError</code> constructor;</li>
<li>itself returns something of type <code>StrictError</code>.</li>
</ul>
</li>
</ol>
<p>And then we simply return a new anonymous class:</p>
<pre><code class="language-typescript">function /* ... */ {
  return class extends StrictError&lt;Type, Cause, Context&gt; {
    readonly name = type;
  }
}
</code></pre>
<p>Except... not quite. In short, an anonymous class has no name. While instances of this class will have names, if we try to analyze the class itself it&apos;ll be nameless. This is fine in most cases, but it&apos;s not what I would consider complete. Have a look:</p>
<pre><code class="language-typescript">class X { name = &quot;X_&quot; }
console.log(X.name) // &quot;X&quot;
console.log(new X().name) // &quot;X_&quot;

const Y = class extends X { name = &quot;Y_&quot; }
console.log(Y.name) // &quot;Y&quot;
console.log(new Y().name) // &quot;Y_&quot;

const z = () =&gt; class extends X { name = &quot;Z_&quot; }
const Z = z();
console.log(Z.name) // &quot;&quot;
console.log(new Z().name) // &quot;Z_&quot;
</code></pre>
<p>Note that in the <code>const Y = class</code> scenario, the class <em>does</em> get a name, but in the Z scenario, where the class is created by an anonymous function, it stays unnamed (<code>Z.name</code> is empty!). So we need <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name#description">some special magic</a> to assign the class a name:</p>
<pre><code class="language-typescript">function /* ... */ {
  const c = class /* ... */ { /* ... */ }
  Object.defineProperty(c, &quot;name&quot;, {
    value: type,
    writable: false,
  });
  return c;
}
</code></pre>
<h3 id="required-fields-only">Required fields only</h3>
<p>In our error instantiation <code>new Disconnect(&quot;We disconnected!&quot;, { cause: y, context: undefined })</code>, we now have to pass <code>context: undefined</code> to the options object. Let&apos;s fix that! What we want is a little more complex than making the fields optional though: when <code>Cause</code> is <code>undefined</code>, the <code>cause</code> property on the second parameter should not exist. Same goes for <code>Context</code> and <code>context</code>. This way, there&apos;s no fiddling with undefined types.</p>
<p>First, we&apos;ll create a simple object type. When <code>Cause</code> is undefined, we want this object to be empty. Otherwise, we&apos;ll want the object to have one required key, <code>from: Cause</code>. This can be done with a conditional operator: <code>Cause extends undefined ? object : { from: Cause }</code>.</p>
<p>By mirroring this type for <code>Context</code>, we now have two objects: one that maybe has a <code>from</code> key and one that maybe has a <code>context</code> key. Now all we need to do is intersect those to create an object that could have either or both the <code>from</code> and <code>context</code> keys, based on the generic type parameters <code>Cause</code> and <code>Context</code>.</p>
<p>Let&apos;s fix the constructor:</p>
<pre><code class="language-typescript">class /* ... */ {
  /* ... */

  constructor(
    readonly message: string,
    readonly options: 
      (
        Cause extends undefined
          ? object
          : { from: Cause }
      ) &amp;
      (
        Context extends undefined
          ? object :
          { context: Context }
      )
  ) {
    super(message, {
      cause: &quot;from&quot; in options ? options.from : undefined,
    });

    this.cause = (&quot;from&quot; in options ? options.from : undefined) as Cause;
    this.context = (
      &quot;context&quot; in options ? options.context : undefined
    ) as Context;
  }
}
</code></pre>
<p>Note that the body of the constructor also changed: we&apos;re doing some ternary logic to ensure that all the assignments work as intended.</p>
<h1 id="its-all-coming-together-now">It&apos;s all coming together now...</h1>
<p>We now have a working version of the example of the start. Go forth and try it! Feel free to include this in your source code. I&apos;ve also packaged the above as <a href="https://github.com/martijnarts/ts-strict-error">ts-strict-error</a>, licensed under the MIT license.</p>
<p>I&apos;d love to hear any feedback in <a href="https://github.com/martijnarts/ts-strict-error/discussions">Github Discussions</a> or on <a href="https://twitter.com/martijnawts">Twitter</a>. I&apos;m also starting a collection of converters from other libraries to <code>StrictError</code>s, and very much welcome PRs. The distribution of that is still up for debate: I&apos;d prefer not to distribute it along with the library itself, but I&apos;m not sure that making a separate package for each converter is right either. If you have any ideas, <a href="https://github.com/martijnarts/ts-strict-error/discussions/1">please tell me</a>.</p>
<!-- this is at the bottom because formatting looks ugly in edit mode, but it works -->
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>It&apos;s not great. The most annoying thing to me is the redundant definition of <code>Type</code> as both a type argument and a function argument. There is a workaround:</p>
<pre><code class="language-typescript"> function inferredCreateStrictError&lt;
  const Cause extends Error | undefined = undefined,
  const Context = undefined
 &gt;() {
   return &lt;const T extends string&gt;(type: T) =&gt; createStrictError&lt;
     T,
     Cause,
     Context
   &gt;(type);
 }
</code></pre>
<p>Would allow us to write a curried definition without the redundant type naming:</p>
<pre><code class="language-typescript">inferredCreateStrictError(IoError, { data: string })(&quot;MyError&quot;)
</code></pre>
<p>But that&apos;s also not very pretty... I guess we&apos;ll have to wait for <a href="https://github.com/microsoft/TypeScript/issues/26242">a solution</a>. <a href="#fnref1" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
<li id="fn2" class="footnote-item"><p>This is because in Typescript, an optional property <code>context?:</code> is not the same as a property that can be undefined <code>context: undefined</code>. <a href="#fnref2" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
</ol>
</section>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Predictable programming 1: how Typescript isn't Rust]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I&apos;ve spent a lot of time building and maintaining React applications. React, with Typescript, is fine, but I keep coming back to the same realisation: it&apos;s not Rust. Over the past few years I&apos;ve been learning Rust, and it has transformed how I write</p>]]></description><link>https://blog.martijnarts.com/predictable-programming-1-how-typescript-isnt-rust/</link><guid isPermaLink="false">645a133593616100014c5a01</guid><dc:creator><![CDATA[Martijn Arts]]></dc:creator><pubDate>Tue, 09 May 2023 09:34:59 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>I&apos;ve spent a lot of time building and maintaining React applications. React, with Typescript, is fine, but I keep coming back to the same realisation: it&apos;s not Rust. Over the past few years I&apos;ve been learning Rust, and it has transformed how I write and think about code. However, as I still have to write React, I&apos;ve been spending a lot of time trying to emulate the benefits of Rust in Typescript.</p>
<p>This is the first part in a series of posts about a &quot;style guide&quot; of sorts for writing Typescript in a way that feels a lot more like Rust. I focus especially on predictability<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>: the minimisation of undefined behaviour and other known unknowns in my codebases.</p>
<h2 id="what-makes-rust-predictable">What makes Rust predictable?</h2>
<p>In programming we often talk about the happy and unhappy paths. Rust is the first language I&apos;ve used that requires you to explicitly define both. Not only that, but Rust also focuses on good ergonomics for doing that.</p>
<p>The most common example is error handling. In other languages, we&apos;re used to seeing <code>throw</code>s and <code>catch</code>es, which allow you to implicitly ignore an unhappy path and assume that the parent scope will deal with it. Rust handles that entirely differently: any errors that could happen must be explicitly declared as part of the return type with a <code>Result&lt;T, E&gt;</code>. You can still ignore an error and pass it to your parent, but it requires you to choose to do that.</p>
<p>However, there is another important way that Rust encourages covering all paths exhaustively. Rust recognizes through enums that many variables carry not only their value, but also a state. With the <code>match</code> expression, you can then force yourself to handle each state explicitly. This provides the ergonomics for the programmer to define and traverse complex code paths, without the mental overhead of implicit state management.</p>
<p>This principle can be applied beyond just Rust&apos;s syntax into development more generally. State machines allow you to strictly model component behaviour; strong typing makes many of these implicit semantic differences explicit; opting in to explicit exception passing allows you to capture almost all possible errors at compile time.</p>
<h2 id="why-not-use-rust">Why not use Rust?</h2>
<p>Wait, you might ask, can&apos;t you use Rust in the frontend now? And that&apos;s true! I could use something like <a href="https://yew.rs/">Yew</a> or <a href="https://dioxuslabs.com/">Dioxus</a> to write my web applications, and I could continue building in React Native (or Swift and Kotlin separately) and maybe use something like <a href="https://redbadger.github.io/crux/">Crux</a> for all the shared business logic.</p>
<p>However, I highly value a mature ecosystem, especially for the frameworks you use. I get the most use out of a framework or tool that I don&apos;t have to debug myself. I&apos;d much prefer to use a commonly used framework for something as complex as UI<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup>, over a new thing that might break in ways that require a lot of effort on my part to debug.</p>
<h2 id="next-up">Next up</h2>
<p>I&apos;m lining up two more posts in this series: one that is more of an experimental style guide on how I write Typescript (which will also be available as a living document on <a href="https://martijnarts.com">my homepage</a>), and another specifically on using state machines in React through XState. Additionally, there&apos;s a post adjacent to this topic on making errors in Typescript better.</p>
<p>If any of this interests you, be sure to sign up for email notifications, add my blog to your RSS app or what have you, or follow me on <a href="https://twitter.com/martijnawts">Twitter</a> or <a href="https://mastodon.nl/@martijnarts">Mastodon</a>.</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>I&apos;ve been calling this &quot;predictable programming&quot; (as seen in this post&apos;s title), but I don&apos;t know that I actually like the name, and friends have said they don&apos;t. Feel free to yell better names at me on Twitter. <a href="#fnref1" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
<li id="fn2" class="footnote-item"><p>Honestly, I feel a lot more confident about using something relatively new in the backend. I&apos;ll be running it in a Docker container, so the environment is stable. Frontends run on all sorts of different devices, browsers, and systems, each with their own weird limitations. I don&apos;t want to be dealing with all that. <a href="#fnref2" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
</ol>
</section>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Towards testable repository automation workflows]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I have recently found myself fully maintaining multiple CI/CD workflows for the first time. It&apos;s been exciting setting up the initial workflow, but now it&apos;s a huge pain making small changes to it. I&apos;m going to be exploring automated testing of the CI/</p>]]></description><link>https://blog.martijnarts.com/towards-testable-repository-automation-workflows/</link><guid isPermaLink="false">6435c04bcbee4a003d0b36a9</guid><dc:creator><![CDATA[Martijn Arts]]></dc:creator><pubDate>Wed, 12 Apr 2023 12:00:49 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>I have recently found myself fully maintaining multiple CI/CD workflows for the first time. It&apos;s been exciting setting up the initial workflow, but now it&apos;s a huge pain making small changes to it. I&apos;m going to be exploring automated testing of the CI/CD workflows themselves <em>while</em> exploring a few specific types of (reusable) workflows.</p>
<p>I&apos;m calling it <a href="https://github.com/martijnarts/relez">relez</a><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>.</p>
<h2 id="repository-automation">Repository automation</h2>
<p>Now, let&apos;s talk about what I&apos;m actually trying to tackle here. CI/CD covers a lot of things, and I&apos;m going after a very small, specific portion of it, in a way tailored for a very particular crowd. There&apos;s probably a better name already out there (and I&apos;m open to better suggestions), but I&apos;m going to term it &quot;repository automation&quot;.</p>
<p>What relez will not cover is the testing and release actions themselves. There are plenty of methods of running tests, builds, and releases, all generic to different degrees. Make, Bazel, doit, npm scripts, etc. all do their job just fine for the purposes of this exploration. relez will cover the questions of when, how, and why those are run.</p>
<p>I&apos;ll be (vaguely) defining a Git flow and collection of automations within relez that I&apos;ve dubbed relez-flows. It&apos;s described in-depth in <a href="https://martijnarts.com/publish/Home">My ideal versioning and releasing workflow</a><sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup>, so what I&apos;d like to cover here is the experimentation around testing those flows. relez-flows will initially cover the most complex workflow described there.</p>
<h3 id="testing-that-the-tests-are-run">Testing that the tests are run</h3>
<p>Maybe more importantly, I&apos;d like to enable my own flows experimentation through test automation. A full set of automations quickly becomes as complex as a minor application, and when making even small changes to CI/CD workflows you are effectively testing in production. You can dogfood in a private/less important repo, but that doesn&apos;t make the A-Z workflow easier to test.</p>
<p>To tackle this challenge, I&apos;m experimenting with testing relez-flows under the name relez-tests. This set of tests and utility tools will test the Github Workflows and Actions, for now using Bats and Github CLI to run the automation in real repositories. By building them in parallel I have a reason to build the tests, as well as a way to test the flows.</p>
<h2 id="relezs-future">relez&apos;s future</h2>
<p>I don&apos;t really feel a need to define a future for relez, but I&apos;d like to be able to use it in my own repositories as soon as possible. It&apos;ll need to make quite a few big steps to make this possible. After finishing the flows, relez will need a method of deployment as well as a method of pushing updates to dependent repositories. relez will need to cover the different versioning workflows to allow using it in my backend repository.</p>
<p>Additionally, it would be nice if this produced something usable by others. If this ends up being useful for myself, I will most likely write more blog posts about it. Potentially, it could also grow into a set of libraries and utilities that could be reused to build other reusable flows.</p>
<p>The dream here is for both relez-flows and relez-tests to be platform-agnostic. At some point, most source management/CI platforms do the same things and it should be possible to deploy the same workflows across them.</p>
<h2 id="in-conclusion">In conclusion...</h2>
<p>I won&apos;t make any promises here on relez&apos;s functionality, direction, or even continuation. Currently, it&apos;s an experiment that hopefully turns into something useful for myself.</p>
<p>If you&apos;re interested in this at all, feel free to come hang out in the repository&apos;s <a href="https://github.com/martijnarts/relez/discussions">discussions</a> tab, star the repo, PR, or message me on Twitter or Mastodon.</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>Feel free to pronounce that however you think is funniest. <a href="#fnref1" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
<li id="fn2" class="footnote-item"><p>Obligatory disclaimer: these flows are obviously not meant to cover all, or even most use cases. They&apos;re meant to cover mine, and maybe like... five other people&apos;s? <a href="#fnref2" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
</ol>
</section>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>