<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Sean Allin Newell</title>
    <description>The latest articles on Forem by Sean Allin Newell (@sirseanofloxley).</description>
    <link>https://forem.com/sirseanofloxley</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F115827%2Ff0a73e9b-2d9c-4977-9029-2b036fbe6f70.jpg</url>
      <title>Forem: Sean Allin Newell</title>
      <link>https://forem.com/sirseanofloxley</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sirseanofloxley"/>
    <language>en</language>
    <item>
      <title>Effect in React</title>
      <dc:creator>Sean Allin Newell</dc:creator>
      <pubDate>Fri, 24 May 2024 13:44:20 +0000</pubDate>
      <link>https://forem.com/sirseanofloxley/effect-in-react-59fb</link>
      <guid>https://forem.com/sirseanofloxley/effect-in-react-59fb</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1624979641604-f01368fab830%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DM3wxMTc3M3wwfDF8c2VhcmNofDQ5fHxsZWdvfGVufDB8fHx8MTcxNjMxNTM3MHww%26ixlib%3Drb-4.0.3%26q%3D80%26w%3D2000" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1624979641604-f01368fab830%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DM3wxMTc3M3wwfDF8c2VhcmNofDQ5fHxsZWdvfGVufDB8fHx8MTcxNjMxNTM3MHww%26ixlib%3Drb-4.0.3%26q%3D80%26w%3D2000" alt="Effect in React"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Emerging from ZIO in the lands of Scala, there is a new ecosystem of functional programming edging its way into TypeScript - &lt;a href="https://effect.website" rel="noopener noreferrer"&gt;Effect&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;ZIO&lt;/em&gt;: For more about this ZIO and Scala lineage, check out this &lt;a href="https://youtu.be/Ei6VTwhI8QQ?si=sIO6xpRI-7WSdyOq" rel="noopener noreferrer"&gt;video from Effect Days 2024&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Behold the power! &lt;em&gt;The following are adaptations taken from the Effect home page, showing examples of what you can do with Effect&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Want to fetch some JSON from an endpoint, handling 200 OK and valid JSON errors, boom.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetchOk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want to retry that endpoint if something goes wrong? Did somebody order a &lt;em&gt;one line fix&lt;/em&gt;?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;--- http.ts 2024-05-21 19:23:24.194145996 +0100
&lt;/span&gt;&lt;span class="gi"&gt;+++ http-with-retry.ts  2024-05-21 19:23:33.524145427 +0100
&lt;/span&gt;&lt;span class="p"&gt;@@ -3,4 +3,5 @@&lt;/span&gt;
     Http.client.fetchOk,
     Http.response.json,
&lt;span class="gi"&gt;+    Effect.retry({ times: 3 })
&lt;/span&gt;   )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How about a more controlled timeout, let's say 3 seconds? Another one liner.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;--- http-with-retry.ts  2024-05-21 19:45:49.874052498 +0100
&lt;/span&gt;&lt;span class="gi"&gt;+++ http-with-retry-and-timeout.ts  2024-05-21 19:45:41.354053136 +0100
&lt;/span&gt;&lt;span class="p"&gt;@@ -3,5 +3,6 @@&lt;/span&gt;
     Http.client.fetchOk,
     Http.response.json,
&lt;span class="gi"&gt;+    Effect.timeout("3 seconds"),
&lt;/span&gt;     Effect.retry({ times: 3 }),
   )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do you want more control on the retry, like an exponential back off? Effect has you covered😎&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;--- http-with-retry-and-timeout.ts  2024-05-21 19:45:41.354053136 +0100
&lt;/span&gt;&lt;span class="gi"&gt;+++ http-with-retry-timeout-backoff.ts  2024-05-21 19:47:56.084044043 +0100
&lt;/span&gt;&lt;span class="p"&gt;@@ -4,5 +4,9 @@&lt;/span&gt;
     Http.response.json,
     Effect.timeout("3 seconds"),
&lt;span class="gd"&gt;-    Effect.retry({ times: 3 }),
&lt;/span&gt;&lt;span class="gi"&gt;+    Effect.retry(
+      Schedule.exponential(1000).pipe(
+        Schedule.compose(Schedule.recurs(3)),
+      ),
+    ),
&lt;/span&gt;   )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want a schema to parse the JSON into? &lt;code&gt;Effect.schema&lt;/code&gt;. Want to throw in an abort controller? Http takes in a &lt;code&gt;signal&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;These are all &lt;em&gt;composable&lt;/em&gt; additions to the program we started with, and each of these pieces can be transparently reused and mixed into other Effects. Kind of like how you snap legos together, or how types help us reason about our program. The Effect type is core to this composition, so it's worth a bit of an introduction&lt;/p&gt;

&lt;h2&gt;
  
  
  The Effect Type
&lt;/h2&gt;

&lt;p&gt;The core of Effect lies in the&lt;a href="https://effect.website/docs/guides/essentials/the-effect-type" rel="noopener noreferrer"&gt;Effect type&lt;/a&gt;, defined &lt;a href="https://github.com/Effect-TS/effect/blob/main/packages/effect/src/Effect.ts#L95" rel="noopener noreferrer"&gt;here&lt;/a&gt;. The docs linked say this about the type parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TSuccess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TRequirements&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Success&lt;/strong&gt; : Represents the type of value that an effect can succeed with when executed. If this type parameter is void, it means the effect produces no useful information, while if it is never, it means the effect runs forever (or until failure).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error&lt;/strong&gt; : Represents the expected errors that can occur when executing an effect. If this type parameter is never, it means the effect cannot fail, because there are no values of type never.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Requirements&lt;/strong&gt; : Represents the contextual data required by the effect to be executed. This data is stored in a collection named Context. If this type parameter is never, it means the effect has no requirements and the Context collection is empty.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;We're mostly going to focus on the first two for this first blog post, but don't sleep on Context (aka requirements), as it is how Effect can do Type-first Dependency Injection, nice! ✨&lt;/p&gt;

&lt;p&gt;So in addition to the semantic meaning we give the three parameters, the other &lt;em&gt;truly important&lt;/em&gt; thing to know about &lt;code&gt;Effect&lt;/code&gt; &lt;strong&gt;is that it is lazy&lt;/strong&gt;. The JSDoc block describing the type from the source code says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;Effect&lt;/code&gt; interface defines a value that lazily describes a workflow or job.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is critical, especially when we bridge the Effectual world to the normal world.&lt;/p&gt;

&lt;p&gt;Let's do that now, with a small React app!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;✋ We're not going to lay down patterns I'm happy with to ship to production (yet!). I'm still learning Effect after all. If you go on the Effect Discord's react channel, there is chatter about a library called &lt;a href="https://github.com/tim-smart/effect-rx" rel="noopener noreferrer"&gt;effect-rx&lt;/a&gt; which is in active development. I'd encourage you to try to use it if you want to be a pioneer, a potential contributor, or want to see what it has to offer.  &lt;/p&gt;

&lt;p&gt;In the next blog post I do on Effect, I'll share another stepping stone, and before too long I'll be confident enough to ship to production with Effect in the client 💪! If you're more interested in server side work, you should check out the &lt;a href="https://github.com/Effect-TS/examples" rel="noopener noreferrer"&gt;examples&lt;/a&gt;and the &lt;a href="https://www.youtube.com/@effect-ts" rel="noopener noreferrer"&gt;talks&lt;/a&gt;, as it is far more "off the shelf" I would say.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  useEffectEffect?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Yes, the library is, unfortunately, called Effect. And in React, we already have a concept with that name - the &lt;code&gt;useEffect&lt;/code&gt; hook. It's worth repeating that &lt;a href="https://react.dev/reference/react/hooks#effect-hooks" rel="noopener noreferrer"&gt;the react docs&lt;/a&gt; highlight &lt;code&gt;useEffect&lt;/code&gt; as a way to "...connect to and synchronize with external systems" for "...non react code...". Some words I've been playing around with to differentiate the two worlds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ReEffect
&lt;/li&gt;
&lt;li&gt;Coalesce
&lt;/li&gt;
&lt;li&gt;Collapse
&lt;/li&gt;
&lt;li&gt;Bridge
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Right now, I hate all of them. We'll see what sticks! 😅&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If we fire up a quick vite app and get the famous counter going, we can begin by installing effect (I use pnpm, feel free to use your favorite package manager):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm create vite my-vue-app &lt;span class="nt"&gt;--template&lt;/span&gt; react-ts
&lt;span class="c"&gt;# Make sure your tsconfig is strict!&lt;/span&gt;

pnpm add effect
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 If you are worried about bundle size, I wouldn't be (yet). At least for our small example things are all still gzipped under 100Kb. The first screenshot below is the default, single chunk of the app we're building today, and the second is with two chunks, a ui chunk for react+react-dom, and a 2nd chunk just for effect. I did not see any difference in bundle size with more specific imports.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F05%2Fimage.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F05%2Fimage.png" alt="Effect in React"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F05%2Fimage-6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F05%2Fimage-6.png" alt="Effect in React"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before we do anything useful, let's test our sanity by doing something we think should be trivial, like saying hello world. In Effect, the simplest thing you can do is succeed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello world!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! If we look at the type signature, it describes the &lt;em&gt;program we created&lt;/em&gt; by using success: &lt;code&gt;Effect&amp;lt;string, never, never&amp;gt;&lt;/code&gt; which means this is a program that when run will yield a string, never error, and requires no context. Cool. 🍨&lt;/p&gt;

&lt;p&gt;This is a lazily evaluated program (sort of... we did not pass in a lazy value, so the string is evaluated, we'll get to that), so we now need to execute or resolve it. Let's do that with &lt;code&gt;runSync&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HiEff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello world!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HiEff&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we see our glorious "Hello world!" in the console, so let's wrap this up in a component and spit it out on the browser&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// declare Effects _outside_ of React.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HiEff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello world!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HelloEff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// execute Effects once, on mount&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HiEff&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hi&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// elsewhere, in App.tsx&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HelloEff&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nice! Feel free to change the text and ensure all the react-refresh / hot module reloading / vite / fast go-go juice all works, it does for me!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🤔 I've explicitly said to put the Effect &lt;em&gt;outside&lt;/em&gt; of React - very purposefully. We'll have to create or leverage Effects that take in user input eventually, but as much as possible put code &lt;strong&gt;outside&lt;/strong&gt; of the render function of a component. That's generally true with and without Effect btw.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now you may be asking... okay, that's nice, &lt;em&gt;but what about promises and all that async stuff?&lt;/em&gt; Well my friend, there's a run promise as well! Keeping our sanity still, let's just try to lift the runSync to promised land and see if we can get it into react, as that part is odd. Since we're going to run our Effect in a Promise, we can't just use the promise in our markup, unless we're on the server and can await or can suspend somehow. The more direct solution is to have some state, just like we would with fetch (or if we were to reach for react-query, just like how react-query stores the response of our n/w call, in state). That looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// declare Effects _outside_ of React.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HiEff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello world!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HelloEffAsync&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Double up as our loading state, and storing the result.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Behaves the same as Promise.resolve&lt;/span&gt;
    &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runPromise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HiEff&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks like it synchronously resolves, so let's add a bit of a delay, with Effect of course, for that one line goodness.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Leverage Effect.delay instead of timeouts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HiEff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello world!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2 seconds&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HelloEffAsync&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runPromise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HiEff&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;look ma', no (explicit) setTimeout or whacky promise shenanigans!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now, with the delay, we actually see the "..." loading string that we used as the initializer to &lt;code&gt;useState&lt;/code&gt; first, then the promise resolves. OK - sanity confirmed, async experienced.&lt;/p&gt;

&lt;h2&gt;
  
  
  ❌ Errors &amp;amp; Effect &amp;amp; React
&lt;/h2&gt;

&lt;p&gt;Our sync and async workflows were simple Success Effects, now let's Fail and see what happens. In Effect, you can 'throw' with &lt;code&gt;Effect.fail(...)&lt;/code&gt;, so let's do that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FailEff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not feeling it...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's see what happens if we runSync a failure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;FiberFailure&lt;span class="o"&gt;)&lt;/span&gt; Error: Error: Not feeling it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oof. An exception, what is this, try-catch town clown 🤡 city?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FailEff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not feeling it&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FailEff&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Exception: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; Exception: (FiberFailure) Error: Not feeling it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At least our program didn't crash, but is there a better way to statically match over the error we know will happen? After all, the type signature of FailEff describes a program that never returns, errors with a string, and needs no context.&lt;/p&gt;

&lt;p&gt;There is! There is a type Effect provides called &lt;code&gt;Exit&lt;/code&gt; and it has async and sync versions of the run methods, so we wrap our result in a structure we can use. &lt;a href="https://effect.website/docs/guides/essentials/running-effects#runsyncexit" rel="noopener noreferrer"&gt;Check out the docs here&lt;/a&gt;, and let's take it for a spin:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🧠 Effects are composable, and you don't need to use what you don't understand. If you see something useful, just start with that and see if it helps you make apps with more confidence.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Cause&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;identity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;effect&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleLeftRight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`Unexpected exit: (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FailEff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not feeling it&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ResultExit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runSyncExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FailEff&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Effect provides these handy match functions to map over all possibilities of a type&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ResultExit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Were we secretely feeling it?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onFailure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;// The type of cause is Cause, which can&lt;/span&gt;
    &lt;span class="c1"&gt;// encapsulate many kinds of failures.&lt;/span&gt;
    &lt;span class="nx"&gt;Cause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// We expect this to be the only path taken, given that our program&lt;/span&gt;
        &lt;span class="c1"&gt;// is Effect&amp;lt;never, string, never&amp;gt;&lt;/span&gt;
        &lt;span class="na"&gt;onFail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;onDie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_defect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unexpected die from a defect&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toJSON&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;onInterrupt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fiberId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="s2"&gt;`Unexpected interrupt on fiber &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fiberId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toJSON&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;onParallel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;handleLeftRight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;onSequential&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;handleLeftRight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;onEmpty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Empty&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; "Not feeling it"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Awesome! You may have experienced one of two emotions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wow - Effect really makes you cross your &lt;code&gt;t&lt;/code&gt;s and dot your &lt;code&gt;i&lt;/code&gt;s.&lt;/li&gt;
&lt;li&gt;That was annoying inside the failure case, if I console log out the cause, I &lt;em&gt;see&lt;/em&gt; that it has a failure / error I can just pull out!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By convention (or maybe coercion) there is a &lt;code&gt;_tag&lt;/code&gt; field on many (all?) types that support this &lt;code&gt;.match&lt;/code&gt; behaviour, so we can do our own kind of collapsing with a switch if we wanted to. This would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ResultExit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Were we secretely feeling it?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onFailure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Cause&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// We can do our own pattern matching&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Fail&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unexpected failure&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is still type safe, as &lt;code&gt;_tag&lt;/code&gt; is discriminating the union and we still get &lt;code&gt;result: string&lt;/code&gt; - but word to the wise, try to leverage match when possible, as it does some narrowing for you (notice the &lt;code&gt;identity&lt;/code&gt; we were able to use in the nested match above), which is quite nice to "zoom" in to the underlying type of each case, when we build the switch ourself, TypeScript has narrowed the type, but we have to do the selection and mapping ourself ( &lt;code&gt;cause.error&lt;/code&gt; ) - which isn't a big deal in this case, but could be for others.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;runPromiseExit&lt;/strong&gt;: Try to switch to an async version of the sync console program shown above.&lt;/p&gt;

&lt;p&gt;Hint - if you are using node v20/bun you can use top level awaits, and the code changes &lt;em&gt;remarkably&lt;/em&gt; little!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now let's bring this home &lt;em&gt;into&lt;/em&gt; react! We can start by just trying to do it all in the component, but pretty soon a wee custom hook will "pop" out of this work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FailEff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I can't even&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2 seconds&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;States&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;failure&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HelloEffAsync&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;effState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setEffState&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;States&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runPromiseExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FailEff&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;FailEffExit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FailEffExit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I CAN even!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nf"&gt;setEffState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;onFailure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;Cause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;onFail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;onDie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_defect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
              &lt;span class="nf"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unexpected defect: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toJSON&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
            &lt;span class="na"&gt;onInterrupt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fiberId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Interrupted: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;fiberId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;onParallel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unexpected parallel error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;onSequential&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unexpected sequential error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;onEmpty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;

          &lt;span class="nf"&gt;setEffState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;failure&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Processing...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-green-500"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-red-500"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's wrap up the reusable promise and exit code, tidy it up a bit, and then we should have halfway decent API to work with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;States&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;failure&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// name pending... naming is hard!&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useEff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TSuccess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;eff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TSuccess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;effState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setEffState&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;States&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TSuccess&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TError&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// still going to opt for a useEffect mount hook to _run it once per mount_&lt;/span&gt;
  &lt;span class="c1"&gt;// but we will take care of aborting&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runPromiseExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolvedValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;setResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolvedValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nf"&gt;setEffState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;onFailure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;setToFailure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nx"&gt;Cause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;onFail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;onDie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_defect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
              &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unexpected defect: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toJSON&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
            &lt;span class="na"&gt;onInterrupt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fiberId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;setToFailure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Interrupted [&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fiberId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] - expecting retry`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;onParallel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unexpected parallel error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;onSequential&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;setToFailure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sequential failure, expecting retry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;onEmpty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;

          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;setToFailure&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;setEffState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;failure&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;effState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The react code then becomes quite clean, akin to a react-query or similar feel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FailEff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I can't even&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1 seconds&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HelloEffAsync&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useEff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FailEff&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Processing...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-green-500"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-red-500"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try switching between fail and succeed, changing the delay, and whatever else comes to mind. You'll see warnings in the console, mostly because React strict mode will mount, unmount, then mount components again which exercises that abort controller we added.&lt;/p&gt;

&lt;h2&gt;
  
  
  🕰 Full Example
&lt;/h2&gt;

&lt;p&gt;Let's take that hook and stick it into a folder with whatever of those names you like most (I think I'll go with &lt;code&gt;re-effect&lt;/code&gt; for now), and boom we've started a react effect library. Now let's really exercise what we have more with some shenanigans around Dates and Time.&lt;/p&gt;

&lt;p&gt;We're going to do something simple for now, just something to make it easier for Hobbits to see when their next meal is and how to plan accordingly. They'll need to know what day it is, the time (in 12 hour format, because 'murica), the season (for what to wear), and their next meal. Let's add some chaos with "business" rules.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We cannot display a time with an even second, only odd. This is because we like jazz too much and cannot live on the down beat, only the off beat &lt;em&gt;(🎺would the off beat be even? or odd? in 'music' beats the first beat is 1 - the down beat, and we emphasize 2 and 4... but in raw timestamps would that be offset? #tangent 🎷)&lt;/em&gt;.
So a timestamp of &lt;code&gt;00:00:03&lt;/code&gt; is fine (HH:mm:ss format), but &lt;code&gt;00:00:02&lt;/code&gt; is NOT okay. We must show an error showing the offending, hideous even second.&lt;/li&gt;
&lt;li&gt;We cannot display a time in the winter, hobbits are not monsters and cannot go out when it is too cold, naturally. Their hair is a fashion statement, not a coat.&lt;/li&gt;
&lt;li&gt;We cannot display a time that is too early, when hobbits should be sleeping (before 5am).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other than that, nothing else can go wrong. Because Effect! (And because we're ignoring timezones, for now. 🤡)&lt;/p&gt;

&lt;p&gt;All of our rules can be determined from a valid &lt;code&gt;Date&lt;/code&gt; object in JS, and we can get the current time with &lt;code&gt;new Date()&lt;/code&gt; - there are other variants of this with libraries and feel free to install and use them - but we should wrap that with Effect so we can compose our program together, and &lt;code&gt;sync&lt;/code&gt; is the &lt;a href="https://effect.website/docs/guides/essentials/creating-effects" rel="noopener noreferrer"&gt;perfect tool&lt;/a&gt; for that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Effect&amp;lt;Date, never, never&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GetToday&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I don't think Date can fail like this, but it certainly can fail if we pass something to it that is invalid, so keep that in mind for any future feature requests or explorations you may elect to do on your own (cough user input cough 💡).&lt;/p&gt;

&lt;p&gt;With the power of Effect, we can pretend stuff exists that we know we can build later - like functions - and start with the modelling of our expected return type and errors. Let's do that now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// we want something like this, so we can show everything we need to&lt;/span&gt;
&lt;span class="c1"&gt;// the hobbitses&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Today&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;monthName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Month&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// union of strings January -&amp;gt; December&lt;/span&gt;
  &lt;span class="nl"&gt;ordinalDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/** 24 hour time */&lt;/span&gt;
  &lt;span class="nl"&gt;hourNum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/** 12 hour time */&lt;/span&gt;
  &lt;span class="nl"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;second&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;meridiem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;am&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;season&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Season&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// union of season names&lt;/span&gt;
  &lt;span class="nl"&gt;nextMeal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Meal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Enough info for name + time of a hobbit meal&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// we can use simple strings for errors, feel free to use a class&lt;/span&gt;
&lt;span class="c1"&gt;// or anything you'd like: https://effect.website/docs/guides/error-management/expected-errors&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;EvenSecond&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SECOND_EVEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TooCold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TOO_COLD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TooEarly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TOO_EARLY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TodayErrors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;EvenSecond&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;TooCold&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;TooEarly&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// we need this type&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TodayEff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Today&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TodayErrors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a lot of little formatting bits here and there, but the &lt;em&gt;gist&lt;/em&gt; is we have that Effect sync that lifts up a Date into the effect world, we have our interface we want React to consume, and we have our errors - now we just need to compose together a new function with our initial function and we should be golden - for now let's just use FlatMap, which will be able to handle the errors / success duality, and return what we need. Oh yeah, and all that stuff I invented like Season/Meal should be made to be real, else it can't compile. 😘&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;TodayEff&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// use Date functions&lt;/span&gt;
  &lt;span class="c1"&gt;// raise invalid states with Effect.fail&lt;/span&gt;
  &lt;span class="c1"&gt;// map to final format of Today with Effect.succeed&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GetToday&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TodayEff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;processDate&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm going to use tailwind to make my app look not like plain html, otherwise the hobbits will laugh at me, but try your hand at it and compare my solution on github to yours! (apologies for the bright light)&lt;/p&gt;

&lt;p&gt;A success state:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F05%2Fimage-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F05%2Fimage-1.png" alt="Effect in React"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few fail states:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F05%2Fimage-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F05%2Fimage-2.png" alt="Effect in React"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F05%2Fimage-3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F05%2Fimage-3.png" alt="Effect in React"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F05%2Fimage-4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F05%2Fimage-4.png" alt="Effect in React"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And some extra conditions if the hobbit might be running late:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F05%2Fimage-5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F05%2Fimage-5.png" alt="Effect in React"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can put a button on the page to remount your component which will re-run your effect, just use &lt;code&gt;key&lt;/code&gt; and increment a number or something (see my linked source code)&lt;/li&gt;
&lt;li&gt;You can use an input and its value existing or not to switch between a TodayEff or an ArbitraryDayEff program, but you'll need to be sure to handle how the Date constructor can fail and create an "Invalid Date" object, yay browsers!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/snewell92/explore-effect" rel="noopener noreferrer"&gt;Here is the source code&lt;/a&gt; - I will continue to update it as I explore more, the commit where this blog was published was &lt;a href="https://github.com/snewell92/explore-effect/commit/ea1583aa86e83f10d89ba76b121f75607650ad25" rel="noopener noreferrer"&gt;ea158&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;I don't quite feel satisfied, so I'm going to do two things next:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Interact with an API, coming soon&lt;/li&gt;
&lt;li&gt;Investigate a managed runtime to provide context (like what&lt;a href="https://www.youtube.com/watch?v=THods1Q_qL8" rel="noopener noreferrer"&gt;this video&lt;/a&gt; shows in Remix, &lt;a href="https://github.com/mikearnaldi/effect-remix-stream/blob/main/app/lib/utilities.ts" rel="noopener noreferrer"&gt;code&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once these two are done, I'll want to try out lots of different kinds of Effects being consumed in React to see where this falls down (as I suspect it will, hence all the work being done in effect-rx). For example, you can declare a Schedule for an Effect, which I think would still work, despite the code we wrote in this post being very "one shot" oriented. And should an async Effect suspend, and how should that work? I'll mostly be looking at react-query and effect-rx / rx-react for inspiration as I move forward.&lt;/p&gt;

&lt;p&gt;I'm off work until July this year, so it's a good chunk of time to explore new things! 😁&lt;/p&gt;

</description>
      <category>effect</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Don't Fear the Rebase 🐮🔔</title>
      <dc:creator>Sean Allin Newell</dc:creator>
      <pubDate>Fri, 29 Mar 2024 11:14:46 +0000</pubDate>
      <link>https://forem.com/sirseanofloxley/dont-fear-the-rebase-35hl</link>
      <guid>https://forem.com/sirseanofloxley/dont-fear-the-rebase-35hl</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1433888376991-1297486ba3f5%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DM3wxMTc3M3wwfDF8c2VhcmNofDJ8fGNyb3d8ZW58MHx8fHwxNzEwNjE4NzM5fDA%26ixlib%3Drb-4.0.3%26q%3D80%26w%3D2000" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1433888376991-1297486ba3f5%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DM3wxMTc3M3wwfDF8c2VhcmNofDJ8fGNyb3d8ZW58MHx8fHwxNzEwNjE4NzM5fDA%26ixlib%3Drb-4.0.3%26q%3D80%26w%3D2000" alt="Don't Fear the Rebase 🐮🔔"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm ashamed to report &lt;a href="https://duckduckgo.com/?t=h_&amp;amp;q=Don%27t+Fear+the+rebase+git&amp;amp;ia=web" rel="noopener noreferrer"&gt;that this title is not unique&lt;/a&gt;, but I felt it in my soul, so I needed to keep it. Please forgive me. 😅&lt;/p&gt;

&lt;p&gt;If you use &lt;a href="https://git-scm.com/" rel="noopener noreferrer"&gt;git&lt;/a&gt;, you likely have experienced the pain of merge conflicts. Due to the decentralised nature of git, merge conflicts are inevitable given that two people work on the same file and area of the codebase.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F03%2Fimage-5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F03%2Fimage-5.png" alt="Don't Fear the Rebase 🐮🔔"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are ways to address merge conflicts, prepare for them, or outright avoid them, so let's try to use these tools and strategies to improve our collaboration with others 💪.&lt;/p&gt;

&lt;p&gt;One of these tools is &lt;a href="https://git-scm.com/docs/rebase" rel="noopener noreferrer"&gt;git rebase&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I want this blog post to address one primary thing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🙈 Rebase is Scary&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This blog post won't leap frog your git skills to that of the master of ancients, but it will hopefully give you a mental model to use and inject some confidence to start failing with safety and fallbacks, moving from the above to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 I can try Rebasing&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Aborting
&lt;/h2&gt;

&lt;p&gt;Many operations in git can &lt;strong&gt;be aborted&lt;/strong&gt; and you will go back to where you were. If you attempt to &lt;strong&gt;git merge&lt;/strong&gt; and something scary happened, just abort! Then you can prepare, backup (pushing to a new branch, for example), and collect yourself. &lt;code&gt;git merge --abort&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F03%2Fimage-6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F03%2Fimage-6.png" alt="Don't Fear the Rebase 🐮🔔"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Reflog
&lt;/h2&gt;

&lt;p&gt;We're going to look at &lt;a href="https://git-scm.com/docs/git-reflog" rel="noopener noreferrer"&gt;the reflog&lt;/a&gt; first. It is an invaluable tool whenever you have "lost" some file or changes, because, oftentimes, you haven't! As long as you have added and committed something, the reflog will be able to find it, even &lt;strong&gt;if you deleted the branch&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F03%2Fimage-4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F03%2Fimage-4.png" alt="Don't Fear the Rebase 🐮🔔"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;reflog of a small project&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you type &lt;code&gt;git reflog&lt;/code&gt; in your terminal, (press &lt;code&gt;q&lt;/code&gt; to escape! Like &lt;code&gt;git log&lt;/code&gt;) you will see the &lt;em&gt;reference log&lt;/em&gt;. &lt;a href="https://git-scm.com/docs/reflog" rel="noopener noreferrer"&gt;The docs&lt;/a&gt; in the 2nd / 3rd sentence give a good summary of what this is, and why it is so, so, useful:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Reflogs are useful in various Git commands, to specify the old value of a reference. For example, HEAD@{2} means "where HEAD used to be two moves ago"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you haven't directly seen it yet, HEAD is just a reference to the tip of the branch you are on. You can act on HEAD with commands like &lt;code&gt;git show HEAD&lt;/code&gt; which prints the commit and diff of your last commit, and you can &lt;code&gt;git diff HEAD&lt;/code&gt; to show what changes on your local filesystem there are, or even &lt;code&gt;git diff HEAD --cached&lt;/code&gt; to see what is the diff between the staged files (the ones that are green in &lt;code&gt;git status&lt;/code&gt;) and the last commit. You can even list all the commits between two references with git log and the &lt;code&gt;..&lt;/code&gt; syntax with &lt;code&gt;git log trunk..HEAD&lt;/code&gt; or &lt;code&gt;git log main..HEAD&lt;/code&gt; depending on what you name your main branch. 💆‍♂️&lt;/p&gt;

&lt;p&gt;The reference log &lt;strong&gt;records all changes to HEAD&lt;/strong&gt; _ &lt;strong&gt;and other references&lt;/strong&gt; _ &lt;strong&gt;.&lt;/strong&gt; It's basically the &lt;em&gt;audit log&lt;/em&gt; of git. And we can access and copy SHAs from the audit log by using &lt;code&gt;git reflog&lt;/code&gt; and copying the SHAs we need to restore our HEAD back, potentially to a time &lt;em&gt;before we attempted a merge or rebase&lt;/em&gt; - which is a neat party trick if anything. Well, a special, nerdy, git party. 🥳&lt;/p&gt;

&lt;p&gt;It's worthwhile to mention that reflog has &lt;em&gt;a lot&lt;/em&gt; of information, so sometimes it is easier to reason about a log or a diff command. Even if the log command is a bit more complicated, it reads nicely with that &lt;code&gt;..&lt;/code&gt; syntax we discussed above. That extra bit of information a well specified log or diff command has is just the extra bit you need to realise &lt;strong&gt;why&lt;/strong&gt; something is conflicting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Show me a list of commits on a branch&lt;/span&gt;
git log main..feat-2

&lt;span class="c"&gt;# show me a list of commits to where i am now&lt;/span&gt;
git log main..HEAD

&lt;span class="c"&gt;# show me a diff between two branches&lt;/span&gt;
git diff main feat-1
git diff feat-1 feat-2
git diff main feat-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Fast Forward Merges
&lt;/h2&gt;

&lt;p&gt;Our overall goal is to be able to use git with &lt;strong&gt;more confidence&lt;/strong&gt; , and hopefully &lt;strong&gt;less merge conflicts&lt;/strong&gt;! So to that end, before rebasing, what if we could just use &lt;code&gt;git merge&lt;/code&gt; and/or &lt;code&gt;git pull&lt;/code&gt; in a 'safer' way that wouldn't cause merge conflicts?&lt;/p&gt;

&lt;p&gt;Enter - Fast Forwarding ⏩&lt;/p&gt;

&lt;p&gt;Fast Forward Merges are a merge strategy to constrain what git is allowed to do to your working tree when merging. A "fast forward" means there is a linear path without any branching or conflicts, so the whole lineage of changes can be "fast forwarded" to the new end state. You may have seen this when git is warning you about it's merge strategy, some message &lt;a href="https://stackoverflow.com/questions/62653114/how-can-i-deal-with-this-git-warning-pulling-without-specifying-how-to-reconci" rel="noopener noreferrer"&gt;like this&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;warning: Pulling without specifying how to reconcile divergent branches is discouraged. You can squelch this message by running one of the following commands sometime before your next pull:  &lt;/p&gt;

&lt;p&gt;git config pull.rebase false  # merge (the default strategy)&lt;br&gt;&lt;br&gt;
 git config pull.rebase true   # rebase&lt;br&gt;&lt;br&gt;
 git config pull.ff only       # fast-forward only  &lt;/p&gt;

&lt;p&gt;You can replace "git config" with "git config --global" to set a default preference for all repositories. You can also pass --rebase, --no-rebase, or --ff-only on the command line to override the configured default per invocation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is nothing to be afraid of! It actually has both of the main concepts we'll cover in this blog post (fast forward and rebase), but in general, this is just asking you &lt;em&gt;how do you want pull to work?&lt;/em&gt; (maybe we should land a PR to simplify that whole message to that one liner question 😜)&lt;/p&gt;

&lt;p&gt;The default strategy is &lt;code&gt;pull.rebase false&lt;/code&gt;. It may have been the cause of that 11:59pm assignment upload to turn it in .com (am I dating myself?) where you were fighting a merge conflict and just gave up and sent your own local copy of the code instead of what was merged into main. 💀&lt;/p&gt;

&lt;p&gt;Whenever you run &lt;code&gt;git pull&lt;/code&gt;, the &lt;code&gt;pull.rebase false&lt;/code&gt; strategy will &lt;strong&gt;merge&lt;/strong&gt; the changes you have fetched from the origin (ie github) into the changes you've made, it is no different than if the remote branch and your local branch &lt;em&gt;were entirely different branches.&lt;/em&gt; It has its uses, and is the default most folks (including myself) are used to. The reasoning being, when executing &lt;code&gt;git pull&lt;/code&gt;, I fully expect git to first &lt;em&gt;fetch&lt;/em&gt; all the changes, and then attempt to &lt;em&gt;merge&lt;/em&gt; them in. It's what it's done since forever. But it is important to recognize that &lt;code&gt;pull.rebase false&lt;/code&gt; means git pull does two operations. &lt;code&gt;git fetch&lt;/code&gt; then &lt;code&gt;git merge&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you want to avoid conflicts with &lt;code&gt;git pull&lt;/code&gt;, consider turning on the &lt;code&gt;pull.ff only&lt;/code&gt; config flag, as that will make pulling &lt;em&gt;disallow&lt;/em&gt; conflicts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config pull.ff only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you try to pull something that would cause a conflict, it will now fail, as you have told git to only do fast forward merges. This is the same as if you did a &lt;code&gt;git merge&lt;/code&gt; and then a &lt;code&gt;git merge --abort&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F03%2Fimage-7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F03%2Fimage-7.png" alt="Don't Fear the Rebase 🐮🔔"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;u&gt;Resolving Impossible Fast Forwards&lt;/u&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If this happens, what do you do? This is my recommendation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Branch off of your branch and push to a new remote branch (take note of the commit, or if multiple this branch name)&lt;/li&gt;
&lt;li&gt;Check out main&lt;/li&gt;
&lt;li&gt;Delete your original branch&lt;/li&gt;
&lt;li&gt;Checkout the remote branch (dbl check the SHA on github/gitlab/origin match your local copy)&lt;/li&gt;
&lt;li&gt;Use the reflog to cherry pick commit(s) on top of this branch, resolving smaller conflicts one by one. (alternatively, you can manually re apply the changes using your new temporary branch as a reference if it small)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;❔ A &lt;a href="https://git-scm.com/docs/git-cherry-pick" rel="noopener noreferrer"&gt;cherry-pick&lt;/a&gt; is a way to apply just a single commit to HEAD - it's kind of like a surgical merge of a single commit to the branch you are currently on. 🔪&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example, the above aborted feat-2 example has one commit that conflicts, but going through the process looks like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# backup our changes before merging&lt;/span&gt;
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; feat-2-san-local-backup
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin feat-2-san-local-backup:feat-2-san-local-backup

&lt;span class="c"&gt;# align our local with what our coworkers pushed to feat-2&lt;/span&gt;
&lt;span class="c"&gt;# this should get feat-2 to match the remote origin (github/gitlab)&lt;/span&gt;
git checkout main
git branch &lt;span class="nt"&gt;-D&lt;/span&gt; feat-2
git pull
git checkout feat-2

&lt;span class="c"&gt;# get the commit we need&lt;/span&gt;
git reflog

&lt;span class="c"&gt;# induce a focused conflict we can fix with cherry-pick&lt;/span&gt;
git chery-pick fff265694ace4d75f4c9366df29b4ff648f34405
vim src/pages/index.astro &lt;span class="c"&gt;# fix the conflict&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of reflog, you could also just show the commits on the branch with a log command: &lt;code&gt;git log main..feat-2-san-local-backup&lt;/code&gt; which will have a lot less output and sometimes just has the one commit you need.&lt;/p&gt;

&lt;p&gt;Instead of inducing a conflict with &lt;code&gt;cherry-pick&lt;/code&gt; , you can elect to open up your change in a UI like github, for example you could have GitHub make a diff between any two branches &lt;a href="https://github.com/snewell92/resume.github.io/compare/feat-1...feat-2" rel="noopener noreferrer"&gt;like this&lt;/a&gt;, and then manually do you changes on the shared branch, and then push a fresh commit - avoiding merge conflicts entirely - at the expense of you copying/pasting or redoing a bit of work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rebase
&lt;/h2&gt;

&lt;p&gt;Rebasing means to &lt;strong&gt;change the base&lt;/strong&gt; of your branch to &lt;strong&gt;some other starting point&lt;/strong&gt;. Rebasing &lt;em&gt;rewinds&lt;/em&gt; all your changes (storing them safely in a stack), then &lt;strong&gt;checks out&lt;/strong&gt; to move to the new base (usually a newer version of main / trunk), and then &lt;strong&gt;re-applies your local rewound&lt;/strong&gt; changes onto the new base.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 If you notice, the resolution suggestion I gave above for the fast-forward only strategy does something very similar, if not identical!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is useful because it can pre-empt any merge conflicts, prevent merge conflicts by preparing a way for fast forward merge, and helps keep main tidy. Pre-empting merge conflicts doesn't mean there won't be any, but if you rebase often, the scope of the merge conflict is contained.&lt;/p&gt;

&lt;p&gt;You don't even need a new branch, if you &lt;code&gt;git fetch&lt;/code&gt; and your &lt;code&gt;git status&lt;/code&gt; is reporting conflicts, you can use the &lt;code&gt;origin/feat-2&lt;/code&gt; style of reference to rebase your local changes on top of the branch from your origin (ie your latest local copy of the state of the branch that github/gitlab etc have). It's a good idea to add the &lt;code&gt;-i&lt;/code&gt; flag to make it an interactive rebase if you are using &lt;code&gt;origin/&lt;/code&gt; style references for branches that have more than 1 / 2 commits, for reasons we will dive into in the next section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F03%2Fimage-9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F03%2Fimage-9.png" alt="Don't Fear the Rebase 🐮🔔"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This may still incur conflicts&lt;/em&gt;, but you will be much more in control of what is happening, and they will happen &lt;em&gt;one at a time&lt;/em&gt;, commit by commit, which often means they are easier to reason about, even if it feels like an onslaught of conflict after conflict (and can be a bit repetitive).&lt;/p&gt;

&lt;h2&gt;
  
  
  Interactive Rebase
&lt;/h2&gt;

&lt;p&gt;Rebase has an option &lt;code&gt;-i&lt;/code&gt; to go into interactive mode, which allows you to tell git what to do when applying each of your rewound changes. This is &lt;strong&gt;very powerful&lt;/strong&gt; as it allows you to re-order, re-word the commit body or message, and squash your commits.&lt;/p&gt;

&lt;p&gt;When you submit the command, you are presented with a text file to edit - when you save+close this file, git will execute the rebase based on this list of instructions. You are kind of programming git here, in rebase syntax. The commented out section tells you about the commands, and you can edit the first word of each line with a single letter or the full command word. The command letter or word plus the git SHA is all git needs, the title is there for us humans.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F03%2Fimage-8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F03%2Fimage-8.png" alt="Don't Fear the Rebase 🐮🔔"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Especially if you use the &lt;code&gt;origin/&lt;/code&gt; reference style &lt;strong&gt;I strongly recommend&lt;/strong&gt; you use an interactive rebase. This is because &lt;strong&gt;if you share commits you likely want to &lt;u&gt;drop&lt;/u&gt; them&lt;/strong&gt; before inducing needless merge conflicts. Many times the rebase rewinding and finding the common ancestor works flawlessly, and you won't see duplicate commits - but if someone else has force pushed or rebased or the lineage has differentiated, then it's a good idea to at least review what the interactive rebase lists as the commits to apply after switching to the new base, and looking at the new base on your remote server (ie GitHub) to see if anyone has squashed things that you still have expanded.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F03%2Fimage-10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F03%2Fimage-10.png" alt="Don't Fear the Rebase 🐮🔔"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;drop the first commit, the new base already includes this&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the above screenshot, I had checked GitHub and saw that the branch I was rebasing onto squashed its commits after rebasing onto main, I had already squashed &lt;em&gt;my&lt;/em&gt; commits onto main in a different way, so I now need to drop my first squash, to not conflict with the origin's squash, and edit my second squash to only include the non-conflicting parts.&lt;/p&gt;

&lt;p&gt;To be clear - this kind of situation is likely to be quite rare for the latest versions of git, as most of the time git can tell what has happened, but it can save a lot of pain by doing that extra step when you see a divergence in git status after a fetch or failed pull.&lt;/p&gt;

&lt;p&gt;When you tell git rebase to do something like an edit/squash/fixup command, git may find conflicts or may pause to let you do that action. The simplest one is just rewording a commit, which you will be presented with a git commit like screen and move on. But for editting or fixup-ing, you will have to tell git rebase when you are done working on the commit.&lt;/p&gt;

&lt;p&gt;You do this by doing whatever the rebase or git status message says, which is often just a &lt;code&gt;git add&lt;/code&gt; / &lt;code&gt;git commit&lt;/code&gt; / &lt;code&gt;git rebase --continue&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F03%2Fimage-11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F03%2Fimage-11.png" alt="Don't Fear the Rebase 🐮🔔"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Feel free to drop a question wherever you found this post, happy to discuss and amend! Happy git-ing 💻, here is a quick reference of some of the commands we used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# the reflog, git's audit trail&lt;/span&gt;
git reflog

&lt;span class="c"&gt;# log .. syntax with HEAD, and branches&lt;/span&gt;
git log main..HEAD
git log main..feat-1
git log feat-1..feat-2
git log origin/feat-1..feat-1

&lt;span class="c"&gt;# diffs&lt;/span&gt;
git diff feat-1..feat-2
git diff origin/feat-1..HEAD
git diff main..HEAD

&lt;span class="c"&gt;# fetch stuff&lt;/span&gt;
git fetch

&lt;span class="c"&gt;# change strategies&lt;/span&gt;
git config pull.ff only
git config pull.rebase &lt;span class="nb"&gt;false
&lt;/span&gt;git config pull.rebase &lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# rebasing&lt;/span&gt;
git rebase
git rebase &lt;span class="nt"&gt;-i&lt;/span&gt;
git rebase &lt;span class="nt"&gt;-i&lt;/span&gt; origin/feat-1
git rebase &lt;span class="nt"&gt;-i&lt;/span&gt; main
git rebase &lt;span class="nt"&gt;-i&lt;/span&gt; feat-2

git rebase &lt;span class="nt"&gt;--continue&lt;/span&gt;

&lt;span class="c"&gt;# may be necessary - push force with lease&lt;/span&gt;
git push &lt;span class="nt"&gt;--force-with-lease&lt;/span&gt;

&lt;span class="c"&gt;## BONUS shell + git aliases!!&lt;/span&gt;

&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;gs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'git status'&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'git checkout'&lt;/span&gt;

&lt;span class="c"&gt;# git rebase shell aliases&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;gbase&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'git rebase'&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;gbae&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'git rebase'&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;grc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'git rebase --continue'&lt;/span&gt;

&lt;span class="c"&gt;# get current branch&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;gcb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'git branch | grep "*" | cut -d" " -f2'&lt;/span&gt;

&lt;span class="c"&gt;# push to the origin and track a new remote branch&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;gpuocb &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gcb&lt;span class="si"&gt;)&lt;/span&gt;
    git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin &lt;span class="nv"&gt;$branch&lt;/span&gt;:&lt;span class="nv"&gt;$branch&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# don't miss out on things!&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;gfomo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'git fetch origin main:main'&lt;/span&gt;

&lt;span class="c"&gt;# Bae Git Aliases for typos&lt;/span&gt;
git config &lt;span class="nt"&gt;--global&lt;/span&gt; alias.bae rebase
git config &lt;span class="nt"&gt;--global&lt;/span&gt; alias.rebae rebase
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>GraphQL &amp; TypeScript Magic</title>
      <dc:creator>Sean Allin Newell</dc:creator>
      <pubDate>Sat, 16 Mar 2024 16:41:02 +0000</pubDate>
      <link>https://forem.com/sirseanofloxley/graphql-typescript-magic-paf</link>
      <guid>https://forem.com/sirseanofloxley/graphql-typescript-magic-paf</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1598621961279-557cec9ba744%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DM3wxMTc3M3wwfDF8c2VhcmNofDEwfHxlbmdpbmV8ZW58MHx8fHwxNzEwNjA3MTc4fDA%26ixlib%3Drb-4.0.3%26q%3D80%26w%3D2000" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1598621961279-557cec9ba744%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DM3wxMTc3M3wwfDF8c2VhcmNofDEwfHxlbmdpbmV8ZW58MHx8fHwxNzEwNjA3MTc4fDA%26ixlib%3Drb-4.0.3%26q%3D80%26w%3D2000" alt="GraphQL &amp;amp; TypeScript Magic"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you haven't heard, there's a new player in town when working with a GraphQL Schema in TypeScript.&lt;/p&gt;

&lt;p&gt;🪄 gql.tada (&lt;a href="https://github.com/0no-co/gql.tada" rel="noopener noreferrer"&gt;repo&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;🌟&lt;/p&gt;

&lt;p&gt;"...&lt;strong&gt;with &lt;code&gt;gql.tada&lt;/code&gt; and &lt;a href="https://github.com/0no-co/graphqlsp" rel="noopener noreferrer"&gt;GraphQLSP&lt;/a&gt; you get on-the-fly, automatically typed GraphQL documents with full editor feedback, auto-completion, and type hints!"&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- gql.tada readme&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What magic is this? Let's see!&lt;/p&gt;

&lt;p&gt;We can use an existing GraphQL API and Schema, so we can focus on just a GQL Client workflow. There's a wonderful list of many GQL apis, so pick your favorite, we're using &lt;a href="https://countries.trevorblades.com/" rel="noopener noreferrer"&gt;Trevor Blade's Countries API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can find the final code &lt;a href="https://github.com/snewell92/gql-tada-explore" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Create a new astro site, then add some relevant libraries following the gql tada instructions and urql instructions, and our editor and code boilerplate will be ready to go.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight r"&gt;&lt;code&gt;&lt;span class="n"&gt;mkdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gql&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tada&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;explore&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gql&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tada&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;explore&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pnpm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;astro&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;latest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# choose strictest TS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pnpm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;astro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;react&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pnpm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lodash.debounce&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gql.tada&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;urql&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;urql&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;graphcache&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pnpm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;no&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;co&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;graphqlsp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lodash.debounce&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;setup instructions&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Update your tsconfig with the following plugin configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"plugins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@0no-co/graphqlsp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://countries.trevorblades.com/graphql"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"tadaOutputLocation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src/graphql-env.d.ts"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;tsconfig.json&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Create a client.ts file to setup urql (cache is optional, but I'd recommend it)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetchExchange&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;urql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cacheExchange&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@urql/exchange-graphcache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://countries.trevorblades.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;exchanges&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;cacheExchange&lt;/span&gt;&lt;span class="p"&gt;({}),&lt;/span&gt; &lt;span class="nx"&gt;fetchExchange&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;src/client.ts&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;src/component&lt;/code&gt; folder, create a react component App.tsx. We will be using &lt;code&gt;&amp;lt;App /&amp;gt;&lt;/code&gt; as a pure SPA so we can get going quickly. We will pull in the client we declared above to provide our gql client to all useQuery calls.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Provider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;urql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello from React&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;src/App.tsx&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Use our App as a client SPA in &lt;code&gt;src/pages/index.astro&lt;/code&gt; as a client only react component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
import { App } from "../components/App";
---
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"icon"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/svg+xml"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/favicon.svg"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"generator"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{Astro.generator}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Country Search&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;App&lt;/span&gt; &lt;span class="na"&gt;client:only=&lt;/span&gt;&lt;span class="s"&gt;"react"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;src/pages/index.astro&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now, to see the magic of our setup, we're going to create a placeholder SearchBar component in src/components and use it in our App.tsx - it won't do anything yet, but that's where we'll start issuing GQL Queries and use the data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Magic
&lt;/h2&gt;

&lt;p&gt;Before proceeding - try to do the following task on your own&lt;/p&gt;

&lt;p&gt;✍️&lt;/p&gt;

&lt;p&gt;Load all continents from one query, so we have each Continent's code and name available. There are not that many Continents on the planet so it we can eager load them all and do our searches in memory. Try your hand at implementing a search by name in memory and display matching continents in a list. &lt;/p&gt;

&lt;p&gt;Now that we are all set up, we can see the 🪄 magic in action. In our SearchBar, import gql from gql.tada, and start authoring a new query in the tagged template literal and you will now get end to end autocomplete and types. Beautiful.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F03%2Fimage--3-.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F03%2Fimage--3-.png" alt="GraphQL &amp;amp; TypeScript Magic"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;type safe completions at query author time&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now that we are authoring queries it's time to see how this works, and how far the intelligence goes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Lints
&lt;/h2&gt;

&lt;p&gt;If we query for continents, including their code and name, but only access the name - we get a TypeScript warning telling us we are, essentially, over fetching!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F03%2Fimage-3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsean.thenewells.us%2Fcontent%2Fimages%2F2024%2F03%2Fimage-3.png" alt="GraphQL &amp;amp; TypeScript Magic"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  All The Types
&lt;/h2&gt;

&lt;p&gt;When a project gets going, you often want to create components to display child nodes of a large query, gql.tada gives us some type helpers so we can cut through the GQL and get to the useful TypeScript bits quickly, look at this code example, taken from &lt;code&gt;src/queries.ts&lt;/code&gt; in the codebase&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;graphql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ResultOf&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gql.tada&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getTopLevel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;graphql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
query getTopLevel {
  continents {
    code
    name
  }

  countries {
    code
    name
    continent { code }
    emoji
    capital
    currencies
  }
}`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TopLevel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ResultOf&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;getTopLevel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Continents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TopLevel&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;continents&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Countries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TopLevel&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;countries&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Continent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Continents&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Countries&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;extract types&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Make an App
&lt;/h2&gt;

&lt;p&gt;Now that we've explored some of the powers and how to make use of gql.tada - the full app in the repo has all the code, but take the time to play with it yourself and fulfill the following requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search all continents by Code or Name.&lt;/li&gt;
&lt;li&gt;Implement tab and enter and click to select a Continent.&lt;/li&gt;
&lt;li&gt;Show a Continent Card that displays all Countries on the selected Continent.&lt;/li&gt;
&lt;li&gt;When you select a Country, show a Country Card that displays information on the selected Country.&lt;/li&gt;
&lt;li&gt;Construct wikipedia links to relevant articles on Countries and Capitals&lt;/li&gt;
&lt;li&gt;Construct financial links to relevant currency comparisons.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some further ideas and explorations you may want to consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Find a GQL API you can locally host, to test how gql.tada + urqlg handles &lt;strong&gt;mutations&lt;/strong&gt; as I have largely skipped over that here.&lt;/li&gt;
&lt;li&gt;Find a &lt;em&gt;large&lt;/em&gt; GQL API and see if the &lt;a href="https://github.com/0no-co/gql.tada/pulls?q=is:pr+is:merged+perf" rel="noopener noreferrer"&gt;latest optimizations&lt;/a&gt; will still retain the snappiness (consider pulling the schema into a local file)&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>4 Day work week remote opening</title>
      <dc:creator>Sean Allin Newell</dc:creator>
      <pubDate>Wed, 20 Jan 2021 05:01:09 +0000</pubDate>
      <link>https://forem.com/sirseanofloxley/4-day-work-week-remote-python-2k11</link>
      <guid>https://forem.com/sirseanofloxley/4-day-work-week-remote-python-2k11</guid>
      <description>&lt;h2&gt;
  
  
  About Us
&lt;/h2&gt;

&lt;p&gt;We're a Scottish tech firm in EdTech. We're breaking into the enterprise market in NA right now and winning bigger clients. We have a four day work week and a strong culture of lived out values.&lt;/p&gt;

&lt;p&gt;Our product offerings include a tms, lms, and customized integrations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.getadministrate.com/about/"&gt;Read more about us on our website&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  About The Opportunity
&lt;/h2&gt;

&lt;p&gt;Administrate is looking to gear up more experienced devs to help mature our platform and take us to the next level. There's never a dull moment! Many systems are in flight to be deprecated, containerized, and migrated to continuous deployment. We have most of our main services containerized and deployed to AWS. We still need a wide range of product work, and are working with enterprise clients in an agile process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skill &amp;amp; Qualifications
&lt;/h2&gt;

&lt;p&gt;This is a senior position, so we'll be expecting a broad range of skills in web development; full stack experience, knowledge of modern front end development, domain driven design, aws, docker, python, typescript, react, etc. All helps.&lt;/p&gt;

&lt;h2&gt;
  
  
  How To Apply
&lt;/h2&gt;

&lt;p&gt;Apply on hire hive and say Sean Newell sent ya 👍.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://my.hirehive.io/administrate/jobs/67536/senior-software-engineer-remote?source=CareerSite"&gt;https://my.hirehive.io/administrate/jobs/67536/senior-software-engineer-remote?source=CareerSite&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Please feel free to leave questions in the comments section.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>programming</category>
      <category>python</category>
      <category>hiring</category>
    </item>
    <item>
      <title>Unity 2020 - pipe dream or reasoned alternative?</title>
      <dc:creator>Sean Allin Newell</dc:creator>
      <pubDate>Fri, 21 Aug 2020 00:48:44 +0000</pubDate>
      <link>https://forem.com/sirseanofloxley/unity-2020-pipe-dream-or-reasoned-alternative-5997</link>
      <guid>https://forem.com/sirseanofloxley/unity-2020-pipe-dream-or-reasoned-alternative-5997</guid>
      <description>&lt;p&gt;What are your thoughts on &lt;a href="https://mobile.twitter.com/hashtag/Ticket2Unity?src=hashtag_click"&gt;#ticket2unity&lt;/a&gt; and the unity 2020 idea in general? Would it have any significant impacy on things in the tech world like data privacy, net neutrality, and others?&lt;/p&gt;

&lt;p&gt;Overall platform: &lt;a href="https://articlesofunity.org/"&gt;https://articlesofunity.org/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>Dockerizing our F# SQLProvider App</title>
      <dc:creator>Sean Allin Newell</dc:creator>
      <pubDate>Fri, 21 Feb 2020 20:37:00 +0000</pubDate>
      <link>https://forem.com/sirseanofloxley/dockerizing-our-f-sqlprovider-app-4d0b</link>
      <guid>https://forem.com/sirseanofloxley/dockerizing-our-f-sqlprovider-app-4d0b</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1f5ky6GZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1559584921-d94fe7f76485%3Fixlib%3Drb-1.2.1%26q%3D80%26fm%3Djpg%26crop%3Dentropy%26cs%3Dtinysrgb%26w%3D2000%26fit%3Dmax%26ixid%3DeyJhcHBfaWQiOjExNzczfQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1f5ky6GZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1559584921-d94fe7f76485%3Fixlib%3Drb-1.2.1%26q%3D80%26fm%3Djpg%26crop%3Dentropy%26cs%3Dtinysrgb%26w%3D2000%26fit%3Dmax%26ixid%3DeyJhcHBfaWQiOjExNzczfQ" alt="" width="880" height="678"&gt;&lt;/a&gt;Photo by &lt;a href="https://unsplash.com/@moritz_photography?utm_source=ghost&amp;amp;utm_medium=referral&amp;amp;utm_campaign=api-credit"&gt;Moritz Kindler&lt;/a&gt; / &lt;a href="https://unsplash.com/?utm_source=ghost&amp;amp;utm_medium=referral&amp;amp;utm_campaign=api-credit"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post continues our journey of statically checked SQL schemas in F# land. We're still using SQLProvider, still on .NET Core 3.1, and still living in CLI world with our console app. All the code lives in a public &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider"&gt;GitLab repo here&lt;/a&gt;. This post starts from &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/-/commit/ccdac180a49e913f1ed58eefc94bc49f2605dc32"&gt;this commit&lt;/a&gt;, and all the work is contained in &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/-/merge_requests/3"&gt;MR#3&lt;/a&gt;. My &lt;a href="https://sean.thenewells.us/f-sqlprovider-in-gitlab-kubernetes-runners/"&gt;previous post&lt;/a&gt; (F# SQLProvider Pipeline on GitLab Kubernetes Runners) refined our CI pipeline to run on a private k8s cluster instead of GitLab's shared runners which will let us iterate faster in this post.&lt;/p&gt;

&lt;p&gt;So, for today, we want to &lt;em&gt;dockerize&lt;/em&gt; our application and build out our pipeline to run &lt;code&gt;docker build&lt;/code&gt; on every commit. Once the pipeline succeeds, we should merge in and tag the freshly minted code to cut a full release.&lt;/p&gt;

&lt;p&gt;Now, because we strictly enforce our schema in a docker container - with our glorious SQLProvider - we should be aware that we may have some bumps in the road. The command we'll be issuing that could cause such bumps is &lt;code&gt;dotnet publish&lt;/code&gt; since that runs the compiler and subsequently our SQLProvider. Let's just try a simple Dockerfile and iterate - #agile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# use 3.1 LTS dotnet SDK image&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/core/sdk:3.1-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;compiler&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# add everything; .dockerignore will filter files and folders&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# run publish!&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet publish &lt;span class="nt"&gt;-f&lt;/span&gt; netcoreapp3.1 &lt;span class="nt"&gt;-o&lt;/span&gt; dist

&lt;span class="c"&gt;# switch to a runtime image w/out full sdk&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/core/runtime:3.1-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runtime&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# copy only the compiled dlls&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=compiler /dist .&lt;/span&gt;

&lt;span class="c"&gt;# setup entrypoint to run the app&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["dotnet", "test-sql-provider.dll"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run this, you'll get an error along the lines of &lt;code&gt;Exception while connecting&lt;/code&gt; which basically means our dotnet publish command, in our local docker daemon, could not find a postgre db to connect to. Luckily the docker build docs ( &lt;code&gt;docker build --help&lt;/code&gt; ) show us a neat little option: &lt;code&gt;--network&lt;/code&gt;. We want to use &lt;a href="https://docs.docker.com/network/host/"&gt;host networking&lt;/a&gt; to expose our postgres db or service. So now, locally, we can run &lt;code&gt;docker build --network host -t test .&lt;/code&gt; and we'll get a docker image built! Yay!&lt;/p&gt;

&lt;p&gt;Now, in order to &lt;em&gt;run&lt;/em&gt; our container locally, you'll also need that &lt;code&gt;--network host&lt;/code&gt; option: &lt;code&gt;docker run -it --rm --network host your-image-tag&lt;/code&gt;. Now your docker container will be able to connect to your local database!&lt;/p&gt;

&lt;h1&gt;
  
  
  CI - The Real Deal
&lt;/h1&gt;

&lt;p&gt;So it works locally - will it work in GitLab's CI?&lt;/p&gt;

&lt;p&gt;Well... almost - in order to get a docker image built in our GitLab CI, we'll need to choose how to do so. In this post I'm opting for the &lt;a href="https://docs.gitlab.com/ee/ci/docker/using_kaniko.html"&gt;Kaniko route&lt;/a&gt; but there is also the Docker-In-Docker route, although that route requires &lt;a href="https://docs.gitlab.com/runner/executors/kubernetes.html#using-dockerdind"&gt;privileged&lt;/a&gt; containers. If at all possible, you should avoid using privileged containers.&lt;/p&gt;

&lt;p&gt;Now, even after choosing kaniko, going through the docs, and getting valid yaml - the road was uh, a little bumpy:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cQm_JZHk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/TryAndTryAgain.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cQm_JZHk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/TryAndTryAgain.PNG" alt="A picture of almost 10 failed builds." width="880" height="663"&gt;&lt;/a&gt;If you don't at first succeed...&lt;/p&gt;

&lt;p&gt;So I'll spare you my pain and jump to explaining Kaniko.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/GoogleContainerTools/kaniko"&gt;Kaniko&lt;/a&gt; is a google project for building OCI images with a plain binary instead of the docker daemon. It's an attempt at being a portable &lt;strong&gt;docker build&lt;/strong&gt;. The reason this is useful is exactly our use case - we have a private CI fleet in k8s and would like to build docker images in a CI job without a privileged container.&lt;/p&gt;

&lt;p&gt;That all sounds great - but the one gotcha I ran into is no matter what I did, _ &lt;strong&gt;I couldn't run&lt;/strong&gt; _ &lt;code&gt;.sh&lt;/code&gt; _ &lt;strong&gt;scripts in a kaniko container&lt;/strong&gt; _. If you try, you'll likely get something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--entrypoint&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; gcr.io/kaniko-project/executor:debug sh

&lt;span class="nv"&gt;$ &lt;/span&gt;vi test.sh
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x test.sh
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;test.sh
&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'hey'&lt;/span&gt;
&lt;span class="c"&gt;# ./test.sh&lt;/span&gt;
sh: test.sh: not found
&lt;span class="nv"&gt;$ &lt;/span&gt;sh &lt;span class="nt"&gt;-c&lt;/span&gt; test.sh
sh: test.sh: not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you know anything about this... drop a comment or @ me on twitter - I would very much like to know why kaniko can't handle shell scripts. I know that the debug tag of the kaniko image has a shell and otherwise it just drops you into the kaniko executor, so it could have something to do with that - but I'm at a loss.&lt;/p&gt;

&lt;p&gt;Why would we need to execute a shell script you ask? Well, to &lt;strong&gt;seed our database&lt;/strong&gt; of course. But where there's a will there's a way! Instead of seeding our database in our CI pipeline with a script like we did , we could just create our own image that puts our schema into the containerized postgres db on startup. So &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/-/tree/master/tools/seeded-postgres-db"&gt;that's what we'll&lt;/a&gt; do!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; postgres:12.1-alpine&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"CREATE SCHEMA IF NOT EXISTS test AUTHORIZATION test_app;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /docker-entrypoint-initdb.d/setup.sql
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; /sql
&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; schema.sql /sql/schema.sql&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /sql/schema.sql &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /docker-entrypoint-initdb.d/setup.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a pretty simple Dockerfile, basically just puts our schema in a special init folder. Postgres has &lt;a href="https://hub.docker.com/_/postgres"&gt;documented environment variables&lt;/a&gt; to set up the test_app user and password, so make sure you set those if you try to pull this image down.&lt;/p&gt;

&lt;p&gt;Now that we have a 'seeded' db of sorts (not seeded with data of course, just the schema) - we can use it in our pipelines!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/pipelines/119114615"&gt;Passed Kaniko Pipeline&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TWUT0yBD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TWUT0yBD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/image.png" alt="" width="701" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/-/jobs/442281303"&gt;Kaniko Step&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--A-5AuMJL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/image-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A-5AuMJL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/image-1.png" alt="" width="806" height="882"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  GitOps Release
&lt;/h1&gt;

&lt;p&gt;Eventually, we will want a release pipeline, and versioning, so we can go ahead and get that set up with tag builds for both the db image and our main app image. I also added some sweet tests so the build would be doing a little bit more than just compilation. I decided to squash the &lt;code&gt;dotnet build&lt;/code&gt; and &lt;code&gt;dotnet test&lt;/code&gt; steps into one build to shave some time. In this case, I think it makes sense to have them as steps in the same job as opposed to different jobs, but your case may vary.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/-/blob/0.1.0/.gitlab-ci.yml#L52-70"&gt;Tagged release of App's docker image&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/-/blob/0.1.0/.gitlab-ci.yml#L88-101"&gt;Tagged release of db docker image&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;We also have pre-releases, tagged with the commit SHAs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was far trickier than I thought at first, and if you look at the whole &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/pipelines"&gt;pipeline history&lt;/a&gt; on my GitLab repo and my &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/-/merge_requests/3/commits"&gt;commit history&lt;/a&gt;, you'll see the experiments I was playing with (multi-lines, buildah, getting kaniko to work right, services, seeding the db...). But I sure did learn a lot! Before this adventure I didn't know about the network flag, kaniko, buildah, or how an F# SQLProvider project could be dockerized.&lt;/p&gt;

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

&lt;p&gt;I think what I have now is quite stable. I feel comfortable with the tests I banged out in this merge request, and now that we are dockerized we can deploy our cat db app anywhere. Also, this MR landed a &lt;code&gt;DB_CONN_STR&lt;/code&gt; environment variable, so as long as we add that &lt;code&gt;--network host&lt;/code&gt; to &lt;code&gt;docker run&lt;/code&gt;, we can pass this env var with the &lt;code&gt;-e&lt;/code&gt; flag to connect our image to any database with the schema it was compiled against - and if we wanted to just pull that down too we have an image with the schema pre-baked. Nice!&lt;/p&gt;

&lt;p&gt;In the next few posts we can now explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding an API layer on top of our core logic&lt;/li&gt;
&lt;li&gt;Adding a front end maybe?&lt;/li&gt;
&lt;li&gt;Building out the pipeline to put the pre-release docker images through the paces (actually run integration tests on the docker image)&lt;/li&gt;
&lt;li&gt;Switch from rebuild to retag on a release build somehow&lt;/li&gt;
&lt;li&gt;Add more advanced db features to showcase the power of F#'s SQLProvider&lt;/li&gt;
&lt;li&gt;Add more useful tests (FsCheck tests are already very powerful for our helper layer)&lt;/li&gt;
&lt;li&gt;Migrate this kind of strategy to a 'real' project (like my &lt;a href="https://gitlab.com/sNewell/trakr"&gt;trakr project&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See y'all soon!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/fWgQH01z4rjwrZckyM/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/fWgQH01z4rjwrZckyM/giphy.gif" alt="" width="480" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>functional</category>
      <category>ci</category>
      <category>docker</category>
    </item>
    <item>
      <title>F# SQLProvider in GitLab Kubernetes Runners</title>
      <dc:creator>Sean Allin Newell</dc:creator>
      <pubDate>Tue, 11 Feb 2020 05:08:42 +0000</pubDate>
      <link>https://forem.com/sirseanofloxley/f-sqlprovider-in-gitlab-kubernetes-runners-2fka</link>
      <guid>https://forem.com/sirseanofloxley/f-sqlprovider-in-gitlab-kubernetes-runners-2fka</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aHWt9V5f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/fran-iv6Xbp11olc-unsplash.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aHWt9V5f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/fran-iv6Xbp11olc-unsplash.jpg" alt="F# SQLProvider in GitLab Kubernetes Runners" width="880" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@fran_?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Fran .&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/lego?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My &lt;a href="https://sean.thenewells.us/type-safe-db-access-every-commit/"&gt;previous post&lt;/a&gt; dove into using GitLab's Shared Runners to verify, in an F# SQLProvider data layer, that the code and schema agree. In this post we'll explore getting off the shared runner infrastructure, and using our own Kubernetes cluster for private CI. I happen to have a Digital Ocean Kubernetes cluster (&lt;a href="https://www.digitalocean.com/products/kubernetes/"&gt;DOK&lt;/a&gt;) - so that's what where my k8s cluster will live, however I'll be sticking with normal &lt;code&gt;helm&lt;/code&gt; and &lt;code&gt;kubectl&lt;/code&gt; commands in this post, so hopefully it'll apply to any k8s installation.&lt;/p&gt;

&lt;p&gt;In this post, I'll be mostly focused on the moving from shared runners to dedicated private CI in our own k8s cluster for our SQLProvider F# project - but that doesn't mean you can't use this post to guide your own CI/CD infrastructure setup for your own project, so cheers 🍻!&lt;/p&gt;

&lt;h1&gt;
  
  
  Getting a Cluster
&lt;/h1&gt;

&lt;p&gt;First thing's first, get a k8s cluster up and running. Using a cloud provider makes this easy-peasy, but there are tons of way to stand up a cluster using various k8s distributions. The key is to make sure you have an up to date kubernetes version, I'll be using &lt;code&gt;v1.16.2&lt;/code&gt;, but anything that supports helm v3 and GitLab's Runners should work.&lt;/p&gt;

&lt;p&gt;In order to verify your cluster is up, try to connect to with some standard tooling like &lt;a href="https://github.com/helm/helm"&gt;&lt;code&gt;helm&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://kubernetes.io/docs/tasks/tools/install-kubectl/"&gt;&lt;code&gt;kubectl&lt;/code&gt;&lt;/a&gt; - if you don't have those tools follow the links and get installing ⏬!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubec version
&lt;span class="nv"&gt;$ &lt;/span&gt;helm version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Connecting the Cluster
&lt;/h1&gt;

&lt;p&gt;Your k8s provider, or your custom setup, will have instructions on how to connect &lt;code&gt;kubectl&lt;/code&gt; and &lt;code&gt;helm&lt;/code&gt; on your local. For Digital Ocean, setting up an API key in your Digitial Ocean account and using the &lt;code&gt;doctl&lt;/code&gt; CLI sets up OAuth tokens for &lt;code&gt;kubectl&lt;/code&gt; to authenticate with the k8s cluster. Once you have that in place, you can run a quick &lt;code&gt;kubectl cluster-info&lt;/code&gt; and &lt;code&gt;kubectl get all --all-namespaces&lt;/code&gt; to see if your local CLI tooling can connect to the cluster.&lt;/p&gt;

&lt;p&gt;Once that's settled, we can install our target workload on the cluster - GitLab Runners that support _ &lt;strong&gt;services&lt;/strong&gt; _. That'll be the key for us, since our SQLProvider based CI setup requires a postgres service.&lt;/p&gt;

&lt;h1&gt;
  
  
  Runner Setup
&lt;/h1&gt;

&lt;p&gt;We'll be following &lt;a href="https://docs.gitlab.com/runner/install/kubernetes.html"&gt;these docs&lt;/a&gt; for the k8s runner setup insetad of the integrated setup. The integrated setup uses helm v2, which needs to install tiller on the cluster which I (and others) don't recommend due to its security implications. We can still use helm charts, but we have helm v3 because we live in the future 🔮.&lt;/p&gt;

&lt;p&gt;First we'll need to add GitLab's helm repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;helm repo add gitlab https://charts.gitlab.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can mess with some yaml, the docs give us the basic picture, but I had to play with it a bit to get the services part working correctly. Here are the fruits of my labor with explainers in comments&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;values.yaml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;gitlabUrl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://gitlab.com/&lt;/span&gt;

&lt;span class="c1"&gt;# Can set with -set runnerRegistrationToken=$CI_TOKEN&lt;/span&gt;
&lt;span class="c1"&gt;# runnerRegistrationToken: ""&lt;/span&gt;

&lt;span class="c1"&gt;# how many jobs should be able to be ran concurrently&lt;/span&gt;
&lt;span class="na"&gt;concurrent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;

&lt;span class="c1"&gt;# how often we check, in seconds, for work&lt;/span&gt;
&lt;span class="na"&gt;checkInterval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;

&lt;span class="c1"&gt;# Role Based Access Control - #veryMuchGoodSecure ?&lt;/span&gt;
&lt;span class="na"&gt;rbac&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;runners&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# default image for jobs&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alpine:3.11.3&lt;/span&gt;
  &lt;span class="na"&gt;locked&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;

&lt;span class="c1"&gt;# This set an env var that presumably allows the postgres service to be used&lt;/span&gt;
&lt;span class="na"&gt;envVars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DOCKER_ALLOWED_SERVICES&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgres:12-alpine"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have this yaml in place we can use helm to deploy the runner&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# pull this from settings -&amp;gt; CI -&amp;gt; runner token&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CI_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;runner-registration-token-from-gitlab

&lt;span class="c"&gt;# install!&lt;/span&gt;
helm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; YOUR_NAMESPACE &lt;span class="se"&gt;\&lt;/span&gt;
  YOUR_RELEASE_NAME &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; values.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  gitlab/gitlab-runner &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="nv"&gt;runnerRegistrationToken&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$CI_TOKEN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note!&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I found &lt;a href="https://stackoverflow.com/a/54116964/2378943"&gt;this stellar SO answer&lt;/a&gt; that informs us about the differences between docker networking and kubernetes networking as it pertains to the executors in GitLab's runners. If you, for example, had a postgres service accessed via the &lt;code&gt;postgres&lt;/code&gt; hostname in a Shared Runner context (docker executor), then you would need to switch to using &lt;code&gt;127.0.0.1:5432&lt;/code&gt;. That change will handle the &lt;code&gt;could not translate host name&lt;/code&gt;. &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/-/merge_requests/2/diffs?commit_id=a6231945317d30a8950e88b0f2c2378aaeff5586"&gt;This commit&lt;/a&gt; is an example refactor of scripts+code that would be necessary to move from shared runners to kubernetes runners.&lt;/p&gt;

&lt;h1&gt;
  
  
  Done!
&lt;/h1&gt;

&lt;p&gt;Now that you have the runners deployed, you can disable shared runners for your GitLab project and push commits and get blazing fast, private CI for just your projects 🎉! This is an example &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/-/merge_requests/2"&gt;merge request&lt;/a&gt; for our ongoing fsharp project that shows all the work we did in this post.&lt;/p&gt;

&lt;p&gt;Now that we have our own private CI, it's time to think about building out a web API layer for our cat app, so we can have something more robust than a CLI app to deploy to. Before that, we'll need dockerize too. Onwards and upwards!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/Ln2dAW9oycjgmTpjX9/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/Ln2dAW9oycjgmTpjX9/giphy.gif" alt="F# SQLProvider in GitLab Kubernetes Runners" width="400" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>functional</category>
      <category>fsharp</category>
      <category>ci</category>
      <category>gitlab</category>
    </item>
    <item>
      <title>Type Safe DB Access checked on every Commit</title>
      <dc:creator>Sean Allin Newell</dc:creator>
      <pubDate>Sun, 09 Feb 2020 04:20:03 +0000</pubDate>
      <link>https://forem.com/sirseanofloxley/type-safe-db-access-checked-on-every-commit-5c1k</link>
      <guid>https://forem.com/sirseanofloxley/type-safe-db-access-checked-on-every-commit-5c1k</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ifg8zPrD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/ryan-quintal-US9Tc9pKNBU-unsplash.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ifg8zPrD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/ryan-quintal-US9Tc9pKNBU-unsplash.jpg" alt="Type Safe DB Access checked on every Commit" width="880" height="660"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@ryanquintal?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Ryan Quintal&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/lego-bricks?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The previous &lt;a href="https://sean.thenewells.us/f-sql-provider-in-net-core-3-1/"&gt;post&lt;/a&gt; had a local setup for type safe SQL access with F#'s Type Provider - &lt;a href="https://github.com/fsprojects/SQLProvider/"&gt;SQLProvider&lt;/a&gt;. In this post we're going to move into CI territory from &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/-/tree/5829e7742e0f05cc4455bd5b7dafb8f6fba6a26d"&gt;this commit in our repo&lt;/a&gt;; enforcing that every commit has to be working with the DB Schema in a valid way. In order to do that - in general for any CI provider - we're going to have to duplicate the steps we did locally on our machines&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;We'll track our work in a branch &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/-/tree/feat/ci-capabilities"&gt;feat/ci-capabilities&lt;/a&gt; and land it in a &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/-/mergerequests/1"&gt;Merge Request&lt;/a&gt;.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  General CI
&lt;/h1&gt;

&lt;p&gt;So, in general, in order to compile our F# project with SQLProvider, we need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A running database

&lt;ul&gt;
&lt;li&gt;With a schema loaded&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;A valid connection string to the running database&lt;/li&gt;
&lt;li&gt;Nuget's restore to pull down the right dlls

&lt;ul&gt;
&lt;li&gt;We have a pre build script that can move those dlls into a spot for the F# compiler.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Depending on your CI provider, there are a few different ways to approach getting a db, loaded with a schema, into your pipeline.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Have a 'stable' external environment accessible to your builds&lt;/li&gt;
&lt;li&gt;Use a feature from your provider (ex &lt;a href="https://docs.gitlab.com/ce/ci/services/postgres.html"&gt;GitLab services&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Leverage docker/custom-images/nested-docker/docker compose to set things up and orchestrate (containerization solves everything right?)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this post we'll explore GitLab's postgres service solution on shared runners.&lt;/p&gt;

&lt;h1&gt;
  
  
  GitLab Shared Runners
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Note - We'll explore how to leverage a personal k8s cluster of runners in the next post.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;GitLab offers free CI/CD for all of their repositories on their &lt;a href="https://docs.gitlab.com/ee/ci/README.html"&gt;shared runner&lt;/a&gt; infrastructure. They have a feature called services (linked above as option 2) that we can leverage to have a postgres db loaded for a job.&lt;/p&gt;

&lt;p&gt;One of the annoying things about CI/CD (this goes for any CI provider) is trying to test out your pipeline/job before just pushing and seeing if it works. That can be... a not-so-great experience. It's one thing to test a shell script locally, but it's another to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have valid yaml&lt;/li&gt;
&lt;li&gt;Have a valid sequence of steps&lt;/li&gt;
&lt;li&gt;Use a feature (aka service) correctly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And if you try to do anything complicated that propogate artifacts or has dependent jobs, it becomes a lot harder to test the pipeline before git push and pray.&lt;/p&gt;

&lt;p&gt;Luckily, GitLab has a nice way to install a tool to help you test locally before pushing. They have a nice set of &lt;a href="https://docs.gitlab.com/runner/install/"&gt;instructions&lt;/a&gt; on their docs, and on my debian buster system it was fairly straight forward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-L&lt;/span&gt; https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sudo &lt;/span&gt;bash &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;gitlab-runner &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; which gitlab-runner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to see if this works, let's just do the simplest build step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "Hello World"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can run this locally with the shell executor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;▶ gitlab-runner &lt;span class="nb"&gt;exec &lt;/span&gt;shell build
Runtime platform &lt;span class="nb"&gt;arch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;amd64 &lt;span class="nv"&gt;os&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux &lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6518 &lt;span class="nv"&gt;revision&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;003fe500 &lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;12.7.1
Running with gitlab-runner 12.7.1 &lt;span class="o"&gt;(&lt;/span&gt;003fe500&lt;span class="o"&gt;)&lt;/span&gt;
Using Shell executor...
executor not supported &lt;span class="nv"&gt;job&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;referee&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;metrics
Running on flantop...
Fetching changes...
Initialized empty Git repository &lt;span class="k"&gt;in&lt;/span&gt; /home/sean/repos/fsharp-sql-provider/builds/0/project-0/.git/
Created fresh repository.
From /home/sean/repos/fsharp-sql-provider
 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;new branch] master -&amp;gt; origin/master
Checking out 5829e774 as master...
Skipping Git submodules setup
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Hello World"&lt;/span&gt;
Hello World
Job succeeded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OH WOWEE.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/l4q7VhGsL6BnXJrc4/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/l4q7VhGsL6BnXJrc4/giphy.gif" alt="Type Safe DB Access checked on every Commit" width="480" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you add an &lt;code&gt;image&lt;/code&gt; to the yaml, you can use the docker executor (assuming you have docker setup locally).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  build:
&lt;span class="gi"&gt;+ image: alpine:3.11.3
&lt;/span&gt;  script:
    - echo "Hello World!"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can use the &lt;code&gt;docker&lt;/code&gt; executor instead of the shell executor, which will be 'more real' since we'll need to have the dotnet core sdk available for our F# builds: &lt;code&gt;$ gitlab-runner exec docker build&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;OH NICE!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/13ByqbM0hgfN7y/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/13ByqbM0hgfN7y/giphy.gif" alt="Type Safe DB Access checked on every Commit" width="480" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Okay, that's all fine and dandy, but we're business-business and need to build enterprising enterprise apps (which of course means we need the dotnet core sdk).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hXxEiWWU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/business-cat-boxes.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hXxEiWWU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/business-cat-boxes.jpg" alt="Type Safe DB Access checked on every Commit" width="580" height="580"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  build:
&lt;span class="gd"&gt;- image: alpine:3.11.3
&lt;/span&gt;&lt;span class="gi"&gt;+ image: mcr.microsoft.com/dotnet/core/sdk:3.1-alpine
&lt;/span&gt;  script:
    - dotnet build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately that won't work. We need a database too - so let's set that up with some services! But wait... we need to load our schema into this database, and our current &lt;code&gt;run-postgres-container.sh&lt;/code&gt; script assumes we're using docker to exec &lt;code&gt;psql&lt;/code&gt; - when we really should be executing &lt;code&gt;psql&lt;/code&gt; in our job, connecting to the service that gitlab will set up for us. We'll have to write a custom script the ci will run, since the service will setup the user for us, but not the schema. Let's call that &lt;code&gt;ci-pg-setup.sh&lt;/code&gt; and it'll look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"CREATE SCHEMA IF NOT EXISTS test AUTHORIZATION test_app;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nv"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pass psql &lt;span class="nt"&gt;-h&lt;/span&gt; postgres &lt;span class="nt"&gt;-U&lt;/span&gt; test_app &lt;span class="nt"&gt;-d&lt;/span&gt; postgres &lt;span class="nt"&gt;-f&lt;/span&gt; -

&lt;span class="nb"&gt;cat &lt;/span&gt;schema.sql | &lt;span class="nv"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pass psql &lt;span class="nt"&gt;-h&lt;/span&gt; postgres &lt;span class="nt"&gt;-U&lt;/span&gt; test_app &lt;span class="nt"&gt;-d&lt;/span&gt; postgres &lt;span class="nt"&gt;-f&lt;/span&gt; -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll also need to make a change to our &lt;code&gt;prebuild.sh&lt;/code&gt; to make it POSIX compliant, using &lt;code&gt;sh&lt;/code&gt; instead of &lt;code&gt;bash&lt;/code&gt; - see &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/commit/74fa61c7777e2344386d4c04268df0f27b3c4122"&gt;this commit&lt;/a&gt; for deets.&lt;/p&gt;

&lt;p&gt;Now before we update the CI yaml we'll need to update our code to conditionally use the &lt;code&gt;postgres&lt;/code&gt; host (due to docker networking in the docker executor). We can do that by conditionally creating a debug symbol in our &lt;code&gt;fsproj&lt;/code&gt; file based on the presence and value of an environment variable, and using that symbol to switch our compile time constant &lt;code&gt;ConnString&lt;/code&gt; in &lt;code&gt;DbAccess.fs&lt;/code&gt;. That's done in &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/commit/eda6a1234edccd340651968b45eeb625eca9045f"&gt;this commit&lt;/a&gt;. It's important to note that we're depending on the &lt;code&gt;psql&lt;/code&gt; tool in our ci setup script, so we need to make sure we add that (&lt;code&gt;apk add postgres-client&lt;/code&gt;) in our job.&lt;/p&gt;

&lt;p&gt;Our full yaml is setup in &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/commit/475f1e950bd7f4c3f6526e8a73390217ecfb13d1"&gt;one commit here&lt;/a&gt; - copied here for your full pleasure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mcr.microsoft.com/dotnet/core/sdk:3.1-alpine&lt;/span&gt;
  &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres:12.1-alpine&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
    &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test_app&lt;/span&gt;
    &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pass&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apk add postgresql-client&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./ci-pg-setup.sh&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CI=TRUE dotnet build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;&lt;strong&gt;And voila! We have a &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/-/jobs/430727670"&gt;passing job&lt;/a&gt; that checked our data layer!&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Break it
&lt;/h1&gt;

&lt;p&gt;Let's break our build!&lt;/p&gt;

&lt;p&gt;One way to break our build would be to write the code without updating our schema script (maybe some dev manually changes their local schema without committing the schema changes) - or to change the schema in a way that causes F# to blow up. Let's do the latter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;schema.sql&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;@@ -20,6 +20,7 @@&lt;/span&gt; CREATE TABLE IF NOT EXISTS test.breed_attributes(
 CREATE TABLE IF NOT EXISTS test.cats(
   id SERIAL PRIMARY KEY,
   name TEXT NOT NULL DEFAULT '',
&lt;span class="gi"&gt;+ age INTEGER NOT NULL DEFAULT 1,
&lt;/span&gt;   breedId INTEGER NOT NULL DEFAULT 1,
   FOREIGN KEY (breedId) REFERENCES test.breeds
 );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this we should expect an error like this (emphasis added by mua):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; error FS0039:
   The field, constructor or member 'Create(breedid, name)' is not defined.
   Maybe you want one of the following:
&lt;span class="gi"&gt;+ Create(age, breedid, name)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the proof is in the pudding! A &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/-/jobs/430735465"&gt;failed build&lt;/a&gt; in our pipeline!&lt;/p&gt;

&lt;p&gt;Let's fix it. Now that Cat's require an &lt;code&gt;age&lt;/code&gt; as per our schema, we should update our data layer, and our domain type too. Those changes will cascade down to the loader and cli programs, but &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/commit/0aea4ffd84436d187e838e00b15e69bade215e56"&gt;after that&lt;/a&gt; the compiler has done all the hunting for us, and &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/pipelines/116234606"&gt;if it compiles&lt;/a&gt;, the program will run soundly.&lt;/p&gt;

&lt;h1&gt;
  
  
  Merging it in
&lt;/h1&gt;

&lt;p&gt;There are a few tweaks I've made before merging:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bundle &lt;code&gt;psql&lt;/code&gt; into the image we're using&lt;/li&gt;
&lt;li&gt;Fix typos here and there&lt;/li&gt;
&lt;li&gt;Use the module alias like we did in &lt;code&gt;Loader&lt;/code&gt; in &lt;code&gt;Cli&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that's &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/-/merge_requests/1"&gt;a wrap&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/xNBcChLQt7s9a/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/xNBcChLQt7s9a/giphy.gif" alt="Type Safe DB Access checked on every Commit" width="180" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Future Work
&lt;/h1&gt;

&lt;p&gt;There's still a lot to do to get to a a point where this is workable for a modern CI/CD web app, in the future I'll put up posts to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instead of shared runners, use a dedicated k8s cluster&lt;/li&gt;
&lt;li&gt;Dockerize the F# application&lt;/li&gt;
&lt;li&gt;Introduce a web layer for API&lt;/li&gt;
&lt;li&gt;Create a deployment&lt;/li&gt;
&lt;li&gt;Automate the deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stay tuned!&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>functional</category>
      <category>gitlab</category>
      <category>sqlprovider</category>
    </item>
    <item>
      <title>F# SQL Provider in .NET Core 3.1</title>
      <dc:creator>Sean Allin Newell</dc:creator>
      <pubDate>Sat, 08 Feb 2020 04:46:44 +0000</pubDate>
      <link>https://forem.com/sirseanofloxley/f-sql-provider-in-net-core-3-1-2c1e</link>
      <guid>https://forem.com/sirseanofloxley/f-sql-provider-in-net-core-3-1-2c1e</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--St0JSEn6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/ryan-quintal-G-HRuwCTR7c-unsplash.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--St0JSEn6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/ryan-quintal-G-HRuwCTR7c-unsplash.jpg" alt="F# SQL Provider in .NET Core 3.1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@ryanquintal?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Ryan Quintal&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/lego?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Preface
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/fsharp/tutorials/type-providers/"&gt;Type providers&lt;/a&gt; have long fascinated me as a killer feature of F#, but I haven't gone whole hog since I was more focused on wanting to use .NET Core F# since .NET Core 2.1 LTS was released. I didn't want to have to finangle with mono or framework on either my work windows 10 box or my linux laptop at home. Luckily, the &lt;a href="https://fsprojects.github.io/SQLProvider/"&gt;SQL Provider&lt;/a&gt; type provider that lets F# programmers write F# flavored LINQ with strong typings right from the database &lt;em&gt;does&lt;/em&gt; work! It works with MySQL and Postgres, on .NET Core 3.1, today! (and probably yesterday and tomorrorw as well).&lt;/p&gt;

&lt;h1&gt;
  
  
  Setting up
&lt;/h1&gt;

&lt;p&gt;You'll need to install a few things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.NET Core 3.1 SDK&lt;/li&gt;
&lt;li&gt;Local Db server (or use Docker!)

&lt;ul&gt;
&lt;li&gt;Ex: Postgres in a container&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Editor with F# intellisense

&lt;ul&gt;
&lt;li&gt;Ex: VSCode + Ionide&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Ability to run scripts

&lt;ul&gt;
&lt;li&gt;Ex: .sh or .ps1&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Terminal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm just going to use VSCode here, but in theory this works in Visual Studio as well - I've tested this on a windows 10 box connected to a remote MySQL server, and on a debian laptop connected to a local postgres server running in a docker container. We'll just be making a small console app, but this works for apps written with Suave, Giraffe, Saturn, and in any netstandard project (although for simplicity sake we'll be making netcore3.1 projects).&lt;/p&gt;

&lt;p&gt;If you're unfamilar with all the hooplah microsoft is throwing our way with netframework, netstandard, netcore, and the lts releases and f# versions I'd encourage you to peruse &lt;a href="https://devblogs.microsoft.com/dotnet/net-core-is-the-future-of-net/"&gt;this blog post&lt;/a&gt; and &lt;a href="https://docs.microsoft.com/en-us/dotnet/standard/components"&gt;these docs&lt;/a&gt;. Something that confused me recently was the shift from &lt;a href="https://docs.microsoft.com/en-us/dotnet/core/packages"&gt;nuget metapackages&lt;/a&gt; to bundled packages &lt;a href="https://github.com/aspnet/Announcements/issues/314"&gt;shipped in the runtime&lt;/a&gt;. The gist is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.NET 5 will unify everything into .NET Core&lt;/li&gt;
&lt;li&gt;Netstandard will likely not apply when everything is net5&lt;/li&gt;
&lt;li&gt;.NET 3 changed how nuget metapackages are handled, and bundles a lot more dlls into the runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These changes are pertinent to F# Type Providers since (especially the SQL Provider) the libraries we use often need to be pointed to a place to pull in compile-time dependency libraries to connect to databases.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setup
&lt;/h1&gt;

&lt;p&gt;Setup is pretty easy once you have an editor and the dotnet sdk since it comes with a nice CLI tool.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;test-sql-provider
dotnet net console &lt;span class="nt"&gt;-lang&lt;/span&gt; f#

&lt;span class="c"&gt;# make sure this runs&lt;/span&gt;
dotnet run

&lt;span class="c"&gt;# and if you have VSCode + Ionide go ahead and fire it up!&lt;/span&gt;
code &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now you have a &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/commit/070c9b6dcf56975c20da0e0392bfc76d7ce5894a"&gt;nice starting point&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note&lt;/em&gt; - on linux you may want to ensure the &lt;code&gt;.fs&lt;/code&gt; and &lt;code&gt;.fsproj&lt;/code&gt; &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/commit/3b1759da4925fcc73e8abc92ffdf439c5aca3a0e"&gt;file are in plain &lt;code&gt;utf-8&lt;/code&gt;&lt;/a&gt; (without bom) and with &lt;code&gt;lf&lt;/code&gt; line endings. ;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Type Provider Setup
&lt;/h2&gt;

&lt;p&gt;In order to work with the SQL Provider, we'll have to create a stable location for the compiler to look for libraries as it is compiling to use in the type provider (ie make connections to the database to generate the types from our sql database).&lt;/p&gt;

&lt;p&gt;I like having conventions enforced by tools, and what better tool than the dotnet build tooling! You can create a 'hook' of sorts that runs an arbitrary command - and that command can populate a well known place (&lt;code&gt;./lib/&lt;/code&gt;) with the appropriate dlls required by the F# type provider so the compiler can consume the types it creates. Nice!&lt;/p&gt;

&lt;p&gt;Let's first create an executable script that just prints something out and wire up the 'hook' in the &lt;code&gt;.fsproj&lt;/code&gt; file. &lt;code&gt;touch prebuild.sh &amp;amp;&amp;amp; chmod +x prebuild.sh&lt;/code&gt; to get the file and put an echo in there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Ahoy!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This should work in windows if windows understands how to run &lt;code&gt;.sh&lt;/code&gt; files - but your mileage may vary, so feel free to swap out the shell file with powershell or even use a node script if you want to.&lt;/p&gt;

&lt;p&gt;Now open the &lt;code&gt;.fsproj&lt;/code&gt; file in your editor and add the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight diff"&gt;&lt;code&gt;   &amp;lt;/PropertyGroup&amp;gt;

+ &amp;lt;Target Name="TypeProviderSetup" **BeforeTargets="Build"** &amp;gt;
&lt;span class="gi"&gt;+ &amp;lt;Exec Command="./prebuild.sh"/&amp;gt;
+ &amp;lt;/Target&amp;gt;
+
&lt;/span&gt;   &amp;lt;ItemGroup&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/commit/3940bc9b03737953644caea70018a95fc33f0170"&gt;see if it&lt;/a&gt; works run a quick &lt;code&gt;dotnet build&lt;/code&gt; to see your echo!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;▶ dotnet build
Microsoft &lt;span class="o"&gt;(&lt;/span&gt;R&lt;span class="o"&gt;)&lt;/span&gt; Build Engine version 16.4.0+e901037fe &lt;span class="k"&gt;for&lt;/span&gt; .NET Core
Copyright &lt;span class="o"&gt;(&lt;/span&gt;C&lt;span class="o"&gt;)&lt;/span&gt; Microsoft Corporation. All rights reserved.

  Restore completed &lt;span class="k"&gt;in &lt;/span&gt;42.01 ms &lt;span class="k"&gt;for&lt;/span&gt; /home/sean/repos/test-sql-provider/test-sql-provider.fsproj.
  test-sql-provider -&amp;gt; /home/sean/repos/test-sql-provider/bin/Debug/netcoreapp3.1/test-sql-provider.dll
  Ahoy!

Build succeeded.
    0 Warning&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
    0 Error&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;

Time Elapsed 00:00:03.53
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now it's time to add packages and figure out how to get the necessary dlls from nuget into the lib folder. I'll just use the &lt;code&gt;dotnet&lt;/code&gt; CLI to add the packages for Postgres and SQLProvider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# gotta do em one at a time&lt;/span&gt;
dotnet add package SQLProvider
dotnet add packages Npgsql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here is the nuget page for the postgres &lt;a href="https://www.nuget.org/packages/Npgsql/"&gt;Npgsql package&lt;/a&gt;. We are particularly interested in the 3.0 dependencies this package requires:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hjLW4pj8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/PosgresDep.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hjLW4pj8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/PosgresDep.jpg" alt="F# SQL Provider in .NET Core 3.1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So this tells us that for apps that target &lt;code&gt;netcoreapp3.x&lt;/code&gt; we need &lt;code&gt;System.Runtime.CompilerServices.Unsafe (&amp;gt;= 4.6.0)&lt;/code&gt;. Since we added the package to our project with &lt;code&gt;dotnet add package&lt;/code&gt; (which uses nuget under the hood) - nuget added the dependnecies to our machine's global nuget cache already. So we actually already have the dll on our machine, and our script can locate it and copy it for us.&lt;/p&gt;

&lt;p&gt;In order to do this in a somewhat robust way, we can leverage nuget to figure out where it stores packages, but then we'll have to figure out the directory structure once we're in the package.&lt;/p&gt;

&lt;p&gt;To get nuget to tell us where it puts things, run &lt;code&gt;dotnet nuget locals global-packages -l&lt;/code&gt;. To figure out where &lt;code&gt;System.Runtime.CompilerServices.Unsafe&lt;/code&gt; lives you can explore the directory structure to reveal this path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;▶ &lt;span class="nb"&gt;ls&lt;/span&gt; ~/.nuget/packages/system.runtime.compilerservices.unsafe/4.6.0/lib/netstandard2.0/
System.Runtime.CompilerServices.Unsafe.dll System.Runtime.CompilerServices.Unsafe.xml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now that we have these two pieces we can finish our bash script (we actually need to use bash if we want to use arrays - you &lt;em&gt;could&lt;/em&gt; read from a txt file that lists the same info out to loop over by reading line by line):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-#!/bin/sh
&lt;/span&gt;&lt;span class="gi"&gt;+#!/bin/bash
&lt;/span&gt;
-echo "Ahoy!"
&lt;span class="gi"&gt;+# Run a restore to be sure we have all the dlls on the system.
+dotnet restore web &amp;gt; /dev/null
+
+# Find nuget's global directory
+nugetRootPath=$(dotnet nuget locals global-packages -l | cut -d' ' -f4)
+nugetRootPath="${nugetRootPath%?}"
+
+# array of ${nameOfDep}|${pathToDllFolder}
+packagesAndPaths=("System.Runtime.CompilerServices.Unsafe|4.6.0/lib/netstandard2.0")
+
+mkdir -p lib
+
+for i in "${packagesAndPaths[@]}"
+do
+ # all these vars split out for clarity
+ name=$(echo "$i" | cut -d'|' -f1)
+ path=$(echo "$i" | cut -d'|' -f2)
+ nugetDll="$name.dll"
+ pathFromNugetRoot="$(echo "$name" | awk '{print tolower($0)}')/$path"
+ fullPath="$nugetRootPath/$pathFromNugetRoot/$nugetDll"
+
+ cp "$fullPath" "./lib/$nugetDll"
+ echo " &amp;gt; Copied $nugetDll into lib for type provider."
+done
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/commit/b419f598abb921158e2a73a7a537242d98956ac4"&gt;Run this&lt;/a&gt; manually to verify that it populates the &lt;code&gt;lib&lt;/code&gt; folder, than &lt;code&gt;rm -rf lib&lt;/code&gt; and run &lt;code&gt;dotnet build&lt;/code&gt; to see...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;▶ dotnet build
Microsoft &lt;span class="o"&gt;(&lt;/span&gt;R&lt;span class="o"&gt;)&lt;/span&gt; Build Engine version 16.4.0+e901037fe &lt;span class="k"&gt;for&lt;/span&gt; .NET Core
Copyright &lt;span class="o"&gt;(&lt;/span&gt;C&lt;span class="o"&gt;)&lt;/span&gt; Microsoft Corporation. All rights reserved.

  Restore completed &lt;span class="k"&gt;in &lt;/span&gt;32.34 ms &lt;span class="k"&gt;for&lt;/span&gt; /home/sean/repos/test-sql-provider/test-sql-provider.fsproj.
  test-sql-provider -&amp;gt; /home/sean/repos/test-sql-provider/bin/Debug/netcoreapp3.1/test-sql-provider.dll
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Copied System.Runtime.CompilerServices.Unsafe.dll into lib &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;type &lt;/span&gt;provider.

Build succeeded.
    0 Warning&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
    0 Error&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;

Time Elapsed 00:00:02.03
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  SQL Time
&lt;/h2&gt;

&lt;p&gt;Let's write a fun SQL Schema! (Did'ya ever think SQL Schemas could be fun? 😹)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- pre-sql - have these run as the postgres user&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="n"&gt;test_app&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="s1"&gt;'pass'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="k"&gt;AUTHORIZATION&lt;/span&gt; &lt;span class="n"&gt;test_app&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- schema sql - run these as the test_app user&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;breeds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;breed_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;attributeId&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;breedId&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributeId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;breedId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;breeds&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;breedId&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;breedId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;breeds&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;owners&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;owner_cats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ownerId&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;catId&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ownerId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;owners&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;catId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cats&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Maybe I went too far... Let's load this up in a docker container with a new volume and then execute the script with psql - all wrapped up &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/commit/1cfbdc69d83e499dde294473c1428ba4a5b7435b"&gt;nice and neat in a script&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;./run-postgres-container.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now normally at this point you'd copy some huge SQL file to load data... but let's use the SQL Provider to do that! Who wants to write or copy inserts anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  F# |&amp;gt; SQL
&lt;/h2&gt;

&lt;p&gt;Following the &lt;a href="http://fsprojects.github.io/SQLProvider/core/general.html"&gt;getting started&lt;/a&gt; and the &lt;a href="http://fsprojects.github.io/SQLProvider/core/postgresql.html"&gt;postgres instructions&lt;/a&gt; for the Type Provider should yield boilerplate like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;FSharp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sql&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;DbVendor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Common&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;DatabaseProviderTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;POSTGRESQL&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;ConnString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Host=localhost;Port=5432;Database=postgres;Username=test_app;Password=pass"&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;Schema&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;ResPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;__&lt;/span&gt;&lt;span class="nc"&gt;SOURCE_DIRECTORY__&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s2"&gt;"./lib"&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;IndivAmount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;UseOptTypes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;true&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;DB&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="nc"&gt;SqlDataProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DatabaseVendor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;DbVendor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ConnectionString&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ConnString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ResolutionPath&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ResPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;IndividualsAmount&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;IndivAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UseOptionTypes&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;UseOptTypes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Owner&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="nn"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetDataContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selectOperations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;SelectOperations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DatabaseSide&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;catDb&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Test&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In VSCode+Ionide that looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IVB61bu---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/VSCodeSql.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IVB61bu---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/VSCodeSql.png" alt="F# SQL Provider in .NET Core 3.1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's load some cats in our database - with only F# code and the schema loaded!&lt;/p&gt;

&lt;p&gt;First let's write a sort of 'domain layer' - this is just normal F# modelling with records and such:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Domain modelling - normal F#!&lt;/span&gt;

&lt;span class="c1"&gt;/// A type of cat (ignoring attributes for now)&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Breed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;/// KITTEH&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Cat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;breed&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Breed&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;/// Make a cat with just a name and breed name, with None for ids&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;mkCat&lt;/span&gt; &lt;span class="n"&gt;catName&lt;/span&gt; &lt;span class="n"&gt;breedName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;catName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;breed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;breedName&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;/// Turn a cat to a string - my how the turn tables!&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;catToStr&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;sprintf&lt;/span&gt; &lt;span class="s2"&gt;"%s(%d)"&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;defaultValue&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Second, let's write some db F# code as 'our db layer' using the SQLProvider boilerplate we wrote above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// plain tables - query on these or get other functions&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;Attributes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;catDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Attributes&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;Cats&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;catDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Cats&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;Breeds&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;catDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Breeds&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;Owners&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;catDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Owners&lt;/span&gt;

&lt;span class="c1"&gt;// join tables&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;BreedAttrs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;catDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BreedAttributes&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;OwnerCats&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;catDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OwnerCats&lt;/span&gt;

&lt;span class="c1"&gt;// creating entity functions&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;createBreed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Breeds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;``Create(name)``&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;createAttribute&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;``Create(description, name)``&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;createCat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Cats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;``Create(breedid, name)``&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;createOwner&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Owners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;``Create(age, name)``&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;createBreedAttr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;BreedAttrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;``Create(attributeid, breedid)``&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;createOwnerCat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Owners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;``Create(age, name)``&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;findBreedIdByName&lt;/span&gt; &lt;span class="n"&gt;bn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nc"&gt;Breeds&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;where&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;take&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Id&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// DB functions&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;insertBreed&lt;/span&gt; &lt;span class="n"&gt;breedName&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;breedEntity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;createBreed&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;breedName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SubmitUpdates&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;breedEntity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Id&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;insertCat&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;breedId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;findBreedIdByName&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;breed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tryHead&lt;/span&gt;
        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;defaultWith&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;insertBreed&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;breed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;catEntity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;createCat&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;breedId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SubmitUpdates&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt; &lt;span class="n"&gt;catEntity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;breed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;breed&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt; &lt;span class="n"&gt;breedId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So far so good, all the types line up - let's compose this into our main program with some helpers along the way to make it super 'fsharpy' and nice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Helpers&lt;/span&gt;

&lt;span class="c1"&gt;/// Take a function, and a tuple, and call the function with the destructured tuple&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;withTuple&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;

&lt;span class="c1"&gt;/// Take a tuple and make a cat in the db, then turn it into a string&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;tupleToCatStr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;withTuple&lt;/span&gt; &lt;span class="n"&gt;mkCat&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;insertCat&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;catToStr&lt;/span&gt;

&lt;span class="c1"&gt;/// Take anything, and give back a zero - a success exit code&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;returnZero&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;rawCatData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Denton"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Gray Tabby"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Mitzie"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Tuxedo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Saphire"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Blue Russian"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Ailee"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Siamese"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Oreo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Tuxedo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Frisky"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Tuxedo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntryPoint&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="n"&gt;argv&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;rawCatData&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="n"&gt;tupleToCatStr&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concat&lt;/span&gt; &lt;span class="s2"&gt;", "&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;printfn&lt;/span&gt; &lt;span class="s2"&gt;"Made a bunch o cats!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt; ***&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;*** &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;returnZero&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now that we have &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/commit/18b8e6918d0bd7bc30a671606a6c418bc03fecb1"&gt;it all setup&lt;/a&gt;, let's run it and see our database in action!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet run &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker &lt;span class="nb"&gt;exec &lt;/span&gt;cat-postgres-db psql &lt;span class="nt"&gt;-U&lt;/span&gt; test_app &lt;span class="nt"&gt;-d&lt;/span&gt; postgres &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"SELECT c.name, b.name FROM test.cats c JOIN test.breeds b ON b.id = c.breedId"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YJPXZRCD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/RunIt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YJPXZRCD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/RunIt.png" alt="F# SQL Provider in .NET Core 3.1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Refactorings
&lt;/h2&gt;

&lt;p&gt;That was a little messy, we can probably extract a lot of reuse from the functions we wrote so create a full fledged CLI cat catalog, so let's refactor before we really crank out some code.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Try your hand at refactoring from &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/commit/18b8e6918d0bd7bc30a671606a6c418bc03fecb1"&gt;this commit&lt;/a&gt; if you want to try your refactor F# chops!&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here ate the modules I split out (these are in compile order):&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The best name in all the land ;P&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Helpers.fs&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// Abstract functional heplers that don't go anywhere particularly specific&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Helpers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Domain.fs&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// Domain modelling - normal F#!&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Domain&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;DbAcces.fs&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// Domain modelling - normal F#!&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;DbAccess&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Loader.fs&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// Load data into the db the easy way!&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Loader&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Program.fs&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// It just calls whatever we're doin (in this case the Loader program)&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;All wrapped up in a &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/commit/d6e0fbb8adb889d3572bada4dfd05cb91e269725"&gt;beautiful commit&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Profit
&lt;/h1&gt;

&lt;p&gt;Let's make a Cat Manager CLI app with the easiest data layer evaaar!&lt;/p&gt;

&lt;p&gt;We'll need to make a new &lt;code&gt;Cli&lt;/code&gt; module for all the... well the cli stuff! There'll be a loop in there and reading/parsing inputs, and then we can write any new db functions in our data layer and do any new modelling we need in the domain layer. If anything gets a little out of hand we can throw something into the helpers to help us out. We won't add any new functionality to the loader for this (in fact we will be removing the call to the Loader module, so it'll be dead weight for now).&lt;/p&gt;

&lt;p&gt;The main CLI loop is as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;latestCatNumber&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;countNumOfCats&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;printfn&lt;/span&gt; &lt;span class="s2"&gt;"Cat Manager CLI 😺: Currently tracking %d cats"&lt;/span&gt; &lt;span class="n"&gt;latestCatNumber&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mutable&lt;/span&gt; &lt;span class="n"&gt;keepGoing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;keepGoing&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;keepGoing&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt;
            &lt;span class="bp"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;
            &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;getInput&lt;/span&gt;
            &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;handleInput&lt;/span&gt;

    &lt;span class="n"&gt;printfn&lt;/span&gt; &lt;span class="s2"&gt;"You're purrfect! 😽"&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then you expand on handleInput:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// Handles input - matches on lowered string and returns to keep going or not&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;handleInput&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;lowered&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ToLowerInvariant&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;lowered&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"?"&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"help"&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;printfn&lt;/span&gt; &lt;span class="s2"&gt;"Available Commands are:&lt;/span&gt;&lt;span class="se"&gt;\n\t&lt;/span&gt;&lt;span class="s2"&gt;%s"&lt;/span&gt; &lt;span class="n"&gt;commandList&lt;/span&gt;
        &lt;span class="bp"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"c"&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"count"&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;latestCatNumber&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;countNumOfCats&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;printfn&lt;/span&gt; &lt;span class="s2"&gt;"Currently tracking %d cats"&lt;/span&gt; &lt;span class="n"&gt;latestCatNumber&lt;/span&gt;
        &lt;span class="bp"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"a"&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"add"&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;handleAdd&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"q"&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"quit"&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here's the &lt;a href="https://gitlab.com/sNewell/fsharp-sql-provider/commit/ff42d21ef3e08510c289e75d4be7971c9006a4da"&gt;full commit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's a screen shot of the program in action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cRZSJd8C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/Running.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cRZSJd8C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sean.thenewells.us/content/images/2020/02/Running.png" alt="F# SQL Provider in .NET Core 3.1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the next part, we need to set up GitLab CI, &lt;a href="https://sean.thenewells.us/type-safe-db-access-every-commit/"&gt;I'll show you how to do it with GitLab's shared runners&lt;/a&gt; and your own runners (on say some kubernetes cluster) - there's some setup you'll have to do to ensure that your F# programs have access to a db with at least the schema preloaded even in the CI environment. We'll then talk about runtime configuration for production environments (often with secret passwords specified with environment variables). And after that, we'll be ready to create a full on web api project in F# using a modern CI/CD pipeline, and even do some clean architecture! Stay tuned!&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>functional</category>
      <category>typeproviders</category>
      <category>sqlprovider</category>
    </item>
    <item>
      <title>Why I Program</title>
      <dc:creator>Sean Allin Newell</dc:creator>
      <pubDate>Fri, 22 Dec 2017 21:47:27 +0000</pubDate>
      <link>https://forem.com/sirseanofloxley/why-i-program-32c</link>
      <guid>https://forem.com/sirseanofloxley/why-i-program-32c</guid>
      <description>&lt;h2&gt;
  
  
  Or, maybe more accurately, why I am a programmer.
&lt;/h2&gt;

&lt;p&gt;People program for many reasons. If your reason differs from my or others' reasons, that's totally cool! We need diversity in this industry, and having differing reasons will broaden our holistic approach to not only problem solving, but to the underlying philosophies that our tools, frameworks, languages, and companies are built on. I'm hopeful that this diversity will grow as our industry continues to mature.&lt;/p&gt;

&lt;p&gt;So, why does Sean Newell program?&lt;/p&gt;

&lt;h2&gt;
  
  
  My Reasons
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;To Learn&lt;/li&gt;
&lt;li&gt;To solve problems&lt;/li&gt;
&lt;li&gt;To help people&lt;/li&gt;
&lt;li&gt;To excercise rigorous creativity&lt;/li&gt;
&lt;li&gt;To explore&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Boom. Blog post over.&lt;/p&gt;

&lt;p&gt;JK&lt;/p&gt;

&lt;p&gt;Let's start a discussion on each of these reasons since discourse is how we refine one another.&lt;/p&gt;

&lt;h3&gt;
  
  
  To Learn
&lt;/h3&gt;

&lt;p&gt;I am a voracious learner. A livid learner. (Insert your profound two-word phrase here). In fact, it gets me into trouble sometimes. Case in point, refer to the &lt;a href="https://sean.thenewells.us/nginx-to-caddy-to-traefik/"&gt;previous blog post&lt;/a&gt; - I probably was &lt;em&gt;minutes&lt;/em&gt; away from getting my nginx config to work, but then I learned about Caddy, then traefik... When does it stop? When do I stop hopping the tool or framework train and ship? Here's the deal though.&lt;/p&gt;

&lt;p&gt;When I have to ship, I don't stop learning.&lt;/p&gt;

&lt;p&gt;Shipping doesn't mark an end to the cycle of exploration and discovery, but rather shipping itself provides a rich set of instructive experiences that can direct my learning further.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Experience is the best teacher."&lt;br&gt;&lt;br&gt;
-Cicero, &lt;em&gt;Oration for Rabirius&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This cycle &lt;em&gt;is inherent in programming&lt;/em&gt;. And not just because of tooling / framework churn, but also because of the nature of our craft. In the book "The Mythical Man Month", the author characterizes programming as dealing with "&lt;em&gt;pure thought stuff&lt;/em&gt;". This means we can learn as we work. Not just through mastery of our skill, but by thinking and iterating on our thoughts. Composing new ideas, and seeing how functions combine in new and interesting ways.&lt;/p&gt;

&lt;p&gt;That's exciting! And it drives me deeper into nooks and crannies of programming to shed light on the parts that I don't quite understand fully yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  To Solve Problems
&lt;/h3&gt;

&lt;p&gt;At my core being I'm probably three things. A sleepy man, a learning man, and a problem solver. It's my default when I'm presented with emotional, relational, or analytical problems. It's just what I do.&lt;/p&gt;

&lt;p&gt;And programming, specifically &lt;em&gt;being a programmer&lt;/em&gt;, demands that I have my problem solver cap on all day.&lt;/p&gt;

&lt;p&gt;Sometimes the problems are rote, have been solved before, or have already been solved and I simply need to implement the solution. Other times, there are interesting twists, constraints, or other circumstances that drives me to think a little deeper, or ask more questions to get to the heart of the problem.&lt;/p&gt;

&lt;p&gt;Either way, a problem is solved, and my brain has to flex. Whether that's categorizing the problem into an existing box, or transforming and composing a problem domain into a workable solution.&lt;/p&gt;

&lt;p&gt;This is where my exploration of functional programming has paid large dividends, because the 'known problem space' now, for me, keeps expanding as I learn about problems other people have been solving for the last ~50 years in category theory, general maths, and other programming communities.&lt;/p&gt;

&lt;h3&gt;
  
  
  To Help People
&lt;/h3&gt;

&lt;p&gt;Helping people is a core component of relating to others in a healthy and productive way, and teaching is often the best teacher, as it forces one to refine ideas and find concise language to deliver an idea well. So being a programmer allows me to flex these relational area well, whether that's teaching or exploring with a co-worker or delivering a feature or product to a customer.&lt;/p&gt;

&lt;p&gt;It's important to note that this is what drives UX and other user-centric methodologies, that's what makes those careers so rewarding. It's a great feeling to know you made a site more accessible, reliable, or usable for real people.&lt;/p&gt;

&lt;h3&gt;
  
  
  To Exercise Rigorous Creativity
&lt;/h3&gt;

&lt;p&gt;Programming, math, and engineering are a special kind of art. These disciplines all deal with certain laws, invariants, or conditions and constraints, but require some desired output. It is then our job to massage the inputs and get the desired output, or prove some desired property (like "this bridge will hold X cars").&lt;/p&gt;

&lt;p&gt;We have to apply a rigor and care to the laws and maths that define our environment, but we have freedom in how we mold and use those laws to bend to our will. It takes practice, discipline, and a certain bull-headed-ness that I find in many programmers. We just don't give up. We press into the discomfort of a tangled problem.&lt;/p&gt;

&lt;p&gt;Then magic happens, a la a creative leap or insight, or a plain educated (or not) guess. Then an idea becomes a reality.&lt;/p&gt;

&lt;p&gt;That's beautiful!&lt;/p&gt;

&lt;h3&gt;
  
  
  To Explore
&lt;/h3&gt;

&lt;p&gt;This may seem redundant - and if so then all I have to say is that &lt;em&gt;repitition is the friend of the adult learner&lt;/em&gt;. But really, I think exploration warrants a different discussion.&lt;/p&gt;

&lt;p&gt;You see, it's expensive to experiment in most industries. That's why there are RnD departments. In software, going back to that &lt;em&gt;pure thought stuff&lt;/em&gt; idea, we can iterate and experiment as cheaply as we can type &lt;code&gt;git checkout -b new-experiment&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Or rather, it &lt;em&gt;can&lt;/em&gt; be cheap to experiment in our craft. As long as we are responsible and aware of how our experiment affects the project and team. Often an experiment can improve an existing tool chain or arcane build script, or provide a new, useful abstraction around some mundane work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fin
&lt;/h3&gt;

&lt;p&gt;So hopefully some of that resonates with you - feel free to drop a comment via disqus down below and tell me some of your perspectives.&lt;/p&gt;

</description>
      <category>softskills</category>
      <category>philosophy</category>
      <category>motivation</category>
    </item>
  </channel>
</rss>
