<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hector Leiva</title>
    <description>The latest articles on DEV Community by Hector Leiva (@hectorleiva).</description>
    <link>https://dev.to/hectorleiva</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%2F250812%2F6ed4080e-3b3b-46a6-8ab0-e82f69e5ae9e.jpeg</url>
      <title>DEV Community: Hector Leiva</title>
      <link>https://dev.to/hectorleiva</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hectorleiva"/>
    <language>en</language>
    <item>
      <title>Object Narrowing in Typescript with Graphile Worker</title>
      <dc:creator>Hector Leiva</dc:creator>
      <pubDate>Wed, 31 Jan 2024 01:53:57 +0000</pubDate>
      <link>https://dev.to/hectorleiva/object-narrowing-in-typescript-with-graphile-worker-45l4</link>
      <guid>https://dev.to/hectorleiva/object-narrowing-in-typescript-with-graphile-worker-45l4</guid>
      <description>&lt;p&gt;I have been using &lt;a href="https://worker.graphile.org"&gt;graphile worker&lt;/a&gt; for some time for my &lt;a href="https://matterofmemory.com"&gt;Matter of Memory&lt;/a&gt; project to run async tasks on my Node.js server.&lt;/p&gt;

&lt;p&gt;Graphile worker has been great for me because it's a library that works with Postgres that allows me to queue jobs and execute them on the server without adding &lt;em&gt;too many&lt;/em&gt; additional layers of complexity for being able to accomplish async tasks. (I'm aware of how popular &lt;a href="https://github.com/taskforcesh/bullmq"&gt;bull&lt;/a&gt; is, but I don't want to add another data-store only for async tasks)&lt;/p&gt;

&lt;p&gt;One of the annoying roadblocks I encountered in my implementation of graphile-worker in Typescript was in making sure that whenever I added a task, that the payload I was giving it matched the task name.&lt;/p&gt;

&lt;p&gt;Here's an example of the issue I was facing (please note: I'm omitting a lot of set-up code for getting graphile-worker actually &lt;em&gt;working&lt;/em&gt;; for that please look at the &lt;a href="https://worker.graphile.org/docs/installation"&gt;excellent docs&lt;/a&gt; that the graphile-worker team has done.)&lt;/p&gt;

&lt;p&gt;Let's say I have 2 background jobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each have different task names&lt;/li&gt;
&lt;li&gt;Each have different payloads
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// backgroundjobs/tasks/files.ts (terrible name I know)&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;Task&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;graphile-worker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ConvertFilePayload&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;fileId&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;fileType&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;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;RecordFileMetaDataPayload&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;metaId&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;convertFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ConvertFilePayload&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;// ...task does work here&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;recordMetaDataOfFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RecordFileMetaDataPayload&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;// ...task does work here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setup the runner to register these tasks:&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;// backgroundjobs/index.ts&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;Runner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TaskSpec&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;graphile-worker&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;convertFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;recordMetaDataOfFile&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;../backgroundjobs/tasks/file&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ConvertFilePayload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RecordFileMetaDataPayload&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;../backgroundjobs/tasks/file&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;runner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Runner&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;BackgroundJobProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;convertFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ConvertFilePayload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;recordMetaDataOfFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RecordFileMetaDataPayload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connectionString&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="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="nx"&gt;runner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nx"&gt;connectionSTring&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;taskList&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;convertFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;recordMetaDataOfFile&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;addJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;jobName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;BackgroundJobProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BackgroundJobProps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;BackgroundJobProps&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TaskSpec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;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;runner&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;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jobName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;spec&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, we go somewhere else on the server and attempt to add a job for &lt;code&gt;convertFile&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// somewhere/else.ts&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;addJob&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;../backgroundJobs.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;backgroundJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;convertFile&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;metaId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="c1"&gt;// this is the wrong payload type, but TS is not erroring out?&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem here is that &lt;code&gt;convertFile&lt;/code&gt; doesn't have a payload of &lt;code&gt;metaId&lt;/code&gt;, but Typescript sees this as fine because &lt;code&gt;metaId&lt;/code&gt; exists as a &lt;em&gt;possible&lt;/em&gt; payload option since &lt;code&gt;RecordFileMetaDataPayload&lt;/code&gt; is also included in &lt;code&gt;BackgroundJobProps&lt;/code&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;convertFile&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;recordMetaDataOfFile&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;{ fileId: string; fileType: string }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ metaId: string }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Why is this bad? It will make us believe that the &lt;code&gt;convertFile&lt;/code&gt; background job is going to work as expected, but we'll end up with a runtime error. The &lt;code&gt;convertFile&lt;/code&gt; task will try and pull out &lt;code&gt;fileId&lt;/code&gt; from the payload, but it'll be &lt;code&gt;undefined&lt;/code&gt; and cause the job to fail.&lt;/p&gt;

&lt;p&gt;With TS object narrowing, we can force a TS error whenever the incorrect payload is attempted against the background task we are targeting.&lt;/p&gt;

&lt;p&gt;Returning to &lt;code&gt;backgroundjobs/index.ts&lt;/code&gt;, we need to modify the Typescript interfaces for the &lt;code&gt;addJob()&lt;/code&gt; function to be able to show us the error if we are using the wrong payload.&lt;/p&gt;

&lt;p&gt;Previously:&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;// backgroundjobs/index.ts&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;addJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;jobName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;BackgroundJobProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BackgroundJobProps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;BackgroundJobProps&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TaskSpec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Object Narrowing:&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;// backgroundjobs/index.ts&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;addJob&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;BackgroundJobProps&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;jobName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BackgroundJobProps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TaskSpec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How this works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;T extends keyof BackgroundJobProps&amp;gt;&lt;/code&gt; is a way to create a Typescript generic &lt;code&gt;T&lt;/code&gt; that will represent one-of-the possible &lt;code&gt;keyof&lt;/code&gt; values of &lt;code&gt;BackgroundJobProps&lt;/code&gt;. This means (in this case) &lt;code&gt;convertFile | recordMetaDataOfFile&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jobName: T&lt;/code&gt; is meant to cast whichever string we write as our first argument when we use &lt;code&gt;addJob("")&lt;/code&gt; and assign it to &lt;code&gt;T&lt;/code&gt; here. We can only pick &lt;code&gt;"convertFile"&lt;/code&gt; or &lt;code&gt;"recordMetaDataOfFile"&lt;/code&gt; since those are the only keys in &lt;code&gt;BackgroundJobProps&lt;/code&gt;. We are going to pick &lt;code&gt;"convertFile"&lt;/code&gt; for the next point.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;options: BackgroundJobProps[T]&lt;/code&gt; since we have cast &lt;code&gt;T&lt;/code&gt; by explicitly declaring it in &lt;code&gt;addJob("convertFile")&lt;/code&gt; what we've done is narrowed down all values in &lt;code&gt;BackgroundJobProps&lt;/code&gt; to only the values that match &lt;code&gt;BackgroundJobProps["convertFile"]&lt;/code&gt; and it returns those values for the &lt;code&gt;options&lt;/code&gt; as the interface that must be matched.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now if we attempt to do:&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;// somewhere/else.ts&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;addJob&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;../backgroundJobs.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;backgroundJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;convertFile&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;metaId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="c1"&gt;// this will now error out and show up as a TS Error&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will now work as expected and return a TS error.&lt;/p&gt;




&lt;p&gt;I had screwed this up myself some time ago which caused an influx of errors that were difficult to find since there were no TS errors being reported, not a fun day at work!&lt;/p&gt;

&lt;p&gt;I hope you all find this helpful and learn from my mistakes.&lt;/p&gt;

&lt;p&gt;-H&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>learning</category>
    </item>
    <item>
      <title>Using Nodejs Buffers to transcribe an Audio file using OpenAI's Whisper service</title>
      <dc:creator>Hector Leiva</dc:creator>
      <pubDate>Tue, 01 Aug 2023 00:14:10 +0000</pubDate>
      <link>https://dev.to/hectorleiva/using-nodejs-buffers-to-transcribe-an-audio-file-using-openais-whisper-service-1ek7</link>
      <guid>https://dev.to/hectorleiva/using-nodejs-buffers-to-transcribe-an-audio-file-using-openais-whisper-service-1ek7</guid>
      <description>&lt;p&gt;I was having a difficult time attempting to do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieve an Audio file from AWS S3&lt;/li&gt;
&lt;li&gt;Have the Audio file &lt;em&gt;in memory&lt;/em&gt; be sent to Open AI's Whisper transcription service to get the transcribed text and save the result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've seen some examples where users &lt;a href="https://stackoverflow.com/questions/75716899/convert-audio-buffer-into-readable-stream-to-use-in-whisper"&gt;were attempting to use a ReadStream instead&lt;/a&gt; and attempt to send that, but that didn't work for me.&lt;/p&gt;

&lt;p&gt;So after a Sunday's worth of effort to figure out how to send this data, I did the following (using this specific version of OpenAI's Node.js API: &lt;a href="https://github.com/openai/openai-node/releases/tag/v4.0.0-beta.7"&gt;4.0.0-beta7&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;s3.service.ts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function async returnBufferFromS3(params: { key: string }) {
      const data = await this.awsS3.send(new GetObjectCommand({
        Bucket: 'some-bucket'
        , Key: params.key
      }));

      const stream = data.Body as Readable;

      return new Promise&amp;lt;Buffer&amp;gt;((resolve, reject) =&amp;gt; {
        const chunks: Buffer[] = [];
        stream.on('data', (chunk) =&amp;gt; chunks.push(chunk));
        stream.on('error', (error) =&amp;gt; reject(error));
        stream.on('end', () =&amp;gt; resolve(Buffer.concat(chunks)));
      });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;openAITranscription.ts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { returnBufferFromS3 } from "s3.service";
import { isUploadable } from "openai/uploads";
...

const openAI = new OpenAI({
  apiKey: 'some-api-key',
  organization: 'some-org-id'
});

function async returnTranscriptionsBasedOnKey(key: string) {
      const buffer = await returnBufferFromS3({ key: 'some-key; });
      const tmp = new Blob([buffer], { type: `test.mp4` });
      const fileLikeBlob = return Object.assign(blob, {
          name: 'test.mp4'
          , lastModified: new Date().getTime()
     });

      if (!isUploadable(fileLikeBlob)) {
        throw new Error(`Unable to upload file to OpenAI due to the following not being uploadable: ${fileLikeBlob}`);
      }

      const openAIResponse = await openAI.audio.transcriptions.create({
        file: fileLikeBlob,
        model: 'whisper-1'
      }

      return openAIResponse;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This seems like an incredible hacky way of completing this task (Buffer -&amp;gt; Blob -&amp;gt; "FileLikeBlob"). &lt;/p&gt;

&lt;p&gt;I'm not proud of it, but I didn't enjoy the time I spent trying to make this work either.&lt;/p&gt;

&lt;p&gt;If anyone in the comments has better recommendations of how to accomplish this same method; I welcome any critique to make it better and to make this information more readily available to more users.&lt;/p&gt;

</description>
      <category>openai</category>
      <category>whisper</category>
      <category>node</category>
    </item>
    <item>
      <title>NightCrit v2.0 (release 1.0)</title>
      <dc:creator>Hector Leiva</dc:creator>
      <pubDate>Mon, 06 Feb 2023 13:34:03 +0000</pubDate>
      <link>https://dev.to/hectorleiva/nightcrit-v20-release-10-4838</link>
      <guid>https://dev.to/hectorleiva/nightcrit-v20-release-10-4838</guid>
      <description>&lt;p&gt;I've completed the first iteration of NightCrit v2.0 in it's current form several days ago:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nightcrit.com" rel="noopener noreferrer"&gt;https://nightcrit.com&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;There were a number of obstacles that had to be overcome:&lt;/p&gt;

&lt;h2&gt;
  
  
  How will Unity render the output of my project?
&lt;/h2&gt;

&lt;p&gt;It is important to me that NightCrit is rendered out so that it is available on the web. A VR-only environment was too limiting for what I wanted.&lt;/p&gt;

&lt;p&gt;I was looking for releasing this project to the largest audience possible, while still attempting to be as immersive as possible. I looked into WebGL and this seemed like it would be the best approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I host this Unity WebGL project?
&lt;/h2&gt;

&lt;p&gt;I already had the domain and a S3 bucket set-up for the Vue project that was NightCrit v1.0. I replaced the contents of that project with the output from Unity.&lt;/p&gt;

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

&lt;p&gt;The biggest headache was getting errors on the &lt;code&gt;index.html&lt;/code&gt; around the &lt;code&gt;Content-Encoding:&lt;/code&gt; not being set correctly. For those that stumble onto this issue in the future (including myself) and are wondering what needs to be updated in order to have &lt;code&gt;.gz&lt;/code&gt; data fetched correctly from S3, this is what needs to be accomplished:&lt;/p&gt;

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

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

&lt;p&gt;The &lt;code&gt;Content-Encoding&lt;/code&gt; &lt;em&gt;needs&lt;/em&gt; to be set on the S3 bucket object itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Plans
&lt;/h2&gt;

&lt;p&gt;There are a few other number of things I wanted to accomplish. The most important thing is that the bones of this project seem to be there; and because of that I felt compelled to publish what I had.&lt;/p&gt;

&lt;p&gt;There will be further updates in trying to make the models better and adding more atmospheric elements to get the feeling that I had in the dream down.&lt;/p&gt;

&lt;p&gt;What's important is to try and show people what I've accomplished thus far.&lt;/p&gt;

&lt;p&gt;Thank you for reading.&lt;/p&gt;

</description>
      <category>announcement</category>
      <category>offers</category>
      <category>devto</category>
      <category>crypto</category>
    </item>
    <item>
      <title>NightCrit v2.0 (in progress)</title>
      <dc:creator>Hector Leiva</dc:creator>
      <pubDate>Tue, 13 Dec 2022 03:31:39 +0000</pubDate>
      <link>https://dev.to/hectorleiva/nightcrit-v20-in-progress-48lp</link>
      <guid>https://dev.to/hectorleiva/nightcrit-v20-in-progress-48lp</guid>
      <description>&lt;p&gt;It's been over a month since I posted &lt;a href="https://dev.to/hectorleiva/nightcrit-v10-238a"&gt;about creating a project about a dream I had dubbed "Nightcrit".&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is an update on what I've currently accomplished:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I've learned how to use &lt;a href="https://www.blender.org/" rel="noopener noreferrer"&gt;Blender&lt;/a&gt; to begin to model out the world of Nightcrit&lt;/li&gt;
&lt;li&gt;I've started reading and watching tutorials about Unity on how to import the models made in Blender and have a user walk through it.&lt;/li&gt;
&lt;li&gt;I've started learning C# in order to program functionality in the world like preventing a user from falling off the map and triggering audio cues.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Blender screenshots:&lt;/p&gt;

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

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

&lt;p&gt;Unity screenshots:&lt;/p&gt;

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

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

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

&lt;p&gt;Some of what remains before I attempt to render this out for the world to see are; the audio messages need to be redone and importing the paintings into the scene.&lt;/p&gt;

&lt;p&gt;I tried to import the audio files as they are and placed them more or less where I had them before in the v1.0 and they do not work in a First Person game engine. It doesn't have the correct "feel", so the audio needs to be re-written and recorded again.&lt;/p&gt;

&lt;p&gt;I hope to have something close to done around this time next month.&lt;/p&gt;

&lt;p&gt;This is my first time using Unity at all and it's been a nice experience so far.&lt;/p&gt;

</description>
      <category>gratitude</category>
    </item>
    <item>
      <title>NightCrit v1.0</title>
      <dc:creator>Hector Leiva</dc:creator>
      <pubDate>Wed, 09 Nov 2022 02:21:49 +0000</pubDate>
      <link>https://dev.to/hectorleiva/nightcrit-v10-238a</link>
      <guid>https://dev.to/hectorleiva/nightcrit-v10-238a</guid>
      <description>&lt;p&gt;I had a dream on October 23rd, 2020 and I'm trying to make it so that other people can experience the dream.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--E6rPuB3i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7hhl4srd5l9xycbutxgu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--E6rPuB3i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7hhl4srd5l9xycbutxgu.png" alt="Image description" width="800" height="767"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nightcrit.com"&gt;Nightcrit&lt;/a&gt; is becoming another art work that I'm using my technologist skill set to implement.&lt;/p&gt;

&lt;p&gt;The first iteration is using Vue.js and basic (i.e. amateur) 3D renderings in order to re-create scenes that fit the feeling of the dream overall.&lt;/p&gt;

&lt;p&gt;This has been a great project to learn a bit of Vue and Blender and see how far I can go with Youtube tutorials and a vision. &lt;/p&gt;

&lt;p&gt;I've also been diving into the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API"&gt;WebAudio API&lt;/a&gt; to try and create atmospheric generative soundscapes that fit the dream. I've only implemented a &lt;a href="https://en.wikipedia.org/wiki/Brownian_noise"&gt;Brown noise&lt;/a&gt; generator; but it is effective in capturing the mood that I'm looking for.&lt;/p&gt;

&lt;p&gt;There are many other projects that people have created already that are influential:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=jjB9I8j01Ko"&gt;Jared Pike's "The Pool Rooms"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/The_Backrooms"&gt;"The Backrooms"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://backroomsgame.io/"&gt;The Backrooms Game&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My next steps are to learn Unity enough to create an artifact that will allow users to open &lt;a href="https://nightcrit.com"&gt;Nightcrit&lt;/a&gt; and be able to move themselves through this landscape and trigger the audio events as they move through it.&lt;/p&gt;

&lt;p&gt;I will have to re-create the 3D apartment building again to allow a virtual user to walk through it, but I'm looking forward to the challenge.&lt;/p&gt;

</description>
      <category>nightcrit</category>
      <category>vue</category>
    </item>
    <item>
      <title>Github Actions and creating a short SHA hash</title>
      <dc:creator>Hector Leiva</dc:creator>
      <pubDate>Fri, 30 Sep 2022 00:49:29 +0000</pubDate>
      <link>https://dev.to/hectorleiva/github-actions-and-creating-a-short-sha-hash-8b7</link>
      <guid>https://dev.to/hectorleiva/github-actions-and-creating-a-short-sha-hash-8b7</guid>
      <description>&lt;p&gt;This will be a quick post as a sort of reminder for myself (and anyone else) on how to actually generate a &lt;em&gt;short&lt;/em&gt; git commit SHA hash in Github actions for a particular build.&lt;/p&gt;

&lt;h3&gt;
  
  
  git commit hashes are important for various reasons
&lt;/h3&gt;

&lt;p&gt;git commit SHA hashes are useful in tagging releases and to know what was the state of the code at a certain point in time.&lt;/p&gt;

&lt;p&gt;git commit SHA hashes are also really long: &lt;code&gt;9c1e1a7f8f3827b11fb9eb34ab0a21afde30e19c&lt;/code&gt; (for example). Sometimes as an engineer you just need to know the first say 6 characters and that's more than enough to know which point in time the code is in.&lt;/p&gt;




&lt;h3&gt;
  
  
  Github provided short commit SHA hashes for some time, but not anymore
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.blog/changelog/2021-01-21-github-actions-short-sha-deprecation/"&gt;Github depreciated the Short SHA environment variable in January, 2021&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This makes sense because if you're going to shorten a git commit SHA, which one are your planning on shortening? You might need the &lt;code&gt;github.event.pull_request_head.sha&lt;/code&gt; &lt;a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request"&gt;for certain workflows&lt;/a&gt; OR you might need the &lt;code&gt;github.sha&lt;/code&gt; which depends on which event triggered that &lt;a href="https://docs.github.com/en/actions/learn-github-actions/contexts#github-context"&gt;specific run&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  My solution to generating a short git commit SHA hash
&lt;/h3&gt;

&lt;p&gt;(Edited: as per &lt;a class="mentioned-user" href="https://dev.to/eazylaykzy"&gt;@eazylaykzy&lt;/a&gt; in Oct 14, 2023, you should use the following due to the deprecation of &lt;code&gt;set-output&lt;/code&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="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;Set short git commit SHA&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vars&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;calculatedSha=$(git rev-parse --short ${{ github.sha }})&lt;/span&gt;
    &lt;span class="s"&gt;echo "COMMIT_SHORT_SHA=$calculatedSha" &amp;gt;&amp;gt; $GITHUB_ENV&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;Confirm git commit SHA output&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo ${{ env.COMMIT_SHORT_SHA }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(deprecated version as of Oct 14, 2023)&lt;/em&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="c1"&gt;# The following is depreciated, left here for historical reference&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;Set short git commit SHA&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vars&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;calculatedSha=$(git rev-parse --short ${{ github.sha }})&lt;/span&gt;
    &lt;span class="s"&gt;echo "::set-output name=short_sha::$calculatedSha"&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;Confirm git commit SHA output&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo ${{ steps.vars.outputs.short_sha }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  How is this different from what's in Stackoverflow
&lt;/h3&gt;

&lt;p&gt;What's annoying about &lt;a href="https://stackoverflow.com/a/59819441/4521535"&gt;this particular Stackoverflow post&lt;/a&gt; is that none of the solutions stated are &lt;em&gt;quite&lt;/em&gt; correct as-of the writing of this post.&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;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Doesn't work since you don't know exactly which &lt;code&gt;HEAD&lt;/code&gt; is being declared in the context of a Github Action runner.&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="s"&gt;echo "::set-output name=sha_short::$(git rev-parse --short ${{ github.event.pull_request.head.sha }})"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Doesn't work because &lt;a href="https://docs.github.com/en/actions/learn-github-actions/expressions#literals"&gt;you can't use&lt;/a&gt; &lt;code&gt;""&lt;/code&gt; &lt;a href="https://docs.github.com/en/actions/learn-github-actions/expressions#literals"&gt;double-quotes when referencing&lt;/a&gt; &lt;code&gt;${{ }}&lt;/code&gt; &lt;a href="https://docs.github.com/en/actions/learn-github-actions/expressions#literals"&gt;tags&lt;/a&gt;. But even if you used the &lt;code&gt;''&lt;/code&gt; single-quotes to wrap this expression, what ends up happening is that &lt;code&gt;sha_short&lt;/code&gt; doesn't equal the string of the git commit SHA, but it's the expression:&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="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--short&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ github.event.pull_request.head.sha &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So any time you use &lt;code&gt;sha_short&lt;/code&gt; later on in your run, it'll have to re-run that entire expression again (which &lt;em&gt;will&lt;/em&gt; return what you want, but there's no need to re-run an expression and instead it's better to store the value).&lt;/p&gt;

&lt;p&gt;I hope that this might help anyone else who is trying to find a simple way to shorten a git commit SHA hash. At least for now!&lt;/p&gt;

</description>
      <category>github</category>
    </item>
    <item>
      <title>What I believe Web 3.0 could be</title>
      <dc:creator>Hector Leiva</dc:creator>
      <pubDate>Wed, 15 Jun 2022 13:11:42 +0000</pubDate>
      <link>https://dev.to/hectorleiva/what-i-believe-web-30-could-be-4gbj</link>
      <guid>https://dev.to/hectorleiva/what-i-believe-web-30-could-be-4gbj</guid>
      <description>&lt;p&gt;The designation “Web 3.0” is often used as an umbrella term for Cryptocurrency, NFT markets, and "the block-chain," but I have greater hopes for the Web’s next iteration. Web 3.0, to me, is about building true decentralization. &lt;a href="https://moxie.org/2022/01/07/web3-first-impressions.html"&gt;Cryptocurrencies and NFT markets do not live up to being the decentralized havens they proclaim to be&lt;/a&gt;, but there are other projects being created now that are the decentralized technologies we need for the future. The Web needs to evolve in order to support users in the inevitable times of crisis brought on by climate change. &lt;/p&gt;

&lt;p&gt;The mesh networks in &lt;a href="https://en.wikipedia.org/wiki/Guifi.net"&gt;Spain&lt;/a&gt;, &lt;a href="https://www.nycmesh.net/"&gt;New York City&lt;/a&gt;, and &lt;a href="http://detroitdjc.org/wireless-mesh/"&gt;Detroit&lt;/a&gt; are examples of a decentralized Web 3.0. These networks begin as hyper-localized communities which function independently of  the global network yet are able to access it as we do now.&lt;/p&gt;

&lt;p&gt;I emphasize hyper-local community networks such as these because when disaster strikes in the form of black-outs, water displacement, fire emergencies, and food shortages, &lt;a href="https://www.csmonitor.com/USA/Society/2018/0507/Months-after-Maria-Puerto-Ricans-take-recovery-into-their-own-hands"&gt;it is your local community network that will be there to help first&lt;/a&gt;. Cryptocurrencies and NFTs break down when they aren't tied to the global network, rendering them useless in times of crisis.&lt;/p&gt;

&lt;p&gt;My vision of Web 3.0 includes AI assistants that rely on hardware and the use of &lt;a href="https://en.wikipedia.org/wiki/Edge_computing"&gt;edge networks&lt;/a&gt; based within a person's home, allowing the AIs to work on behalf of the user without needing to always be connected "on-line". These assistants will never need to connect to a third-party unless explicitly directed by the user. &lt;/p&gt;

&lt;p&gt;I imagine a class of devices that are designed to be off-the-grid first and work without ever needing the internet, such as wearable IoT devices that help you learn more about yourself instead of spying on you and reporting to the rest of Silicon Valley.&lt;/p&gt;

&lt;p&gt;Creators in the &lt;a href="https://fediverse.party/"&gt;Fediverse&lt;/a&gt; are designing software, such as &lt;a href="https://ipfs.io/"&gt;IPFS&lt;/a&gt; and &lt;a href="https://joinmastodon.org/"&gt;Mastodon&lt;/a&gt;, that work without needing to be connected to the internet by default.&lt;/p&gt;

&lt;p&gt;This Web, which prioritizes local communities over the global network, can free us from relying on Amazon, Google, Apple, etc. and pull us toward each other. It encourages us to have an eye towards conservation of our communities instead of consumerism.&lt;/p&gt;

&lt;p&gt;A decentralized Web comes closer every day as more people join &lt;a href="https://discord.com/"&gt;Discords&lt;/a&gt; as their preferred community environment. Seeking alternatives to Facebook, Instagram, Tiktok, and Twitter, people join Discords because they want fewer hateful messages from anonymous users and more channels of communication to talk with friends about favorite TV shows.&lt;/p&gt;

&lt;p&gt;In the end, I hope for a Web that returns power to the users. Building the Web with decentralization as a priority will make us resilient against the perils of the future. We need a Web in which privacy is a foundational property, where apps work for the users again. Instead of making a few companies ultra-wealthy, this Web can become a tool that helps everyone, both in our daily lives and in times of crisis.&lt;/p&gt;

</description>
      <category>web3</category>
      <category>decentralization</category>
      <category>architecture</category>
    </item>
    <item>
      <title>How writing state machines made me feel like a programmer</title>
      <dc:creator>Hector Leiva</dc:creator>
      <pubDate>Sat, 26 Sep 2020 15:28:58 +0000</pubDate>
      <link>https://dev.to/hectorleiva/how-writing-state-machines-made-me-feel-like-a-programmer-2ndc</link>
      <guid>https://dev.to/hectorleiva/how-writing-state-machines-made-me-feel-like-a-programmer-2ndc</guid>
      <description>&lt;p&gt;I've been working as a "web developer" since 2012. &lt;/p&gt;

&lt;p&gt;I am a self-taught programmer who has worked on custom PHP frameworks, Wordpress, Drupal, Node.js apps, and Python microservices.&lt;/p&gt;

&lt;p&gt;It wasn't until 2019, when I wrote my first state machine, that I felt like I wrote something as a programmer.&lt;/p&gt;

&lt;p&gt;Writing a state machine for work using &lt;a href="https://xstate.js.org/"&gt;xstate&lt;/a&gt; and knowing that over a year later that code is still in production has given me a greater sense of self-worth.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are state machines?
&lt;/h2&gt;

&lt;p&gt;Before I get ahead of myself, for anyone who isn't aware, &lt;a href="https://xstate.js.org/"&gt;xstate&lt;/a&gt; is a Javascript library that implements finite state machines.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Finite-state_machine"&gt;Finite state machines&lt;/a&gt; are a model of how to describe the world.&lt;/p&gt;

&lt;p&gt;A machine exists in a state and transitions are created to show how the machine can move to different states or go back into the same state (a vending machine is a finite state machine).&lt;/p&gt;

&lt;p&gt;These definitions at the start didn't do much for me, and they certainly didn't make me feel like a better programmer by "knowing" what they meant. I had to actually create something using these concepts in order to feel like I got it.&lt;/p&gt;

&lt;h2&gt;
  
  
  UI's have always been complicated
&lt;/h2&gt;

&lt;p&gt;I work in healthcare. Healthcare apps are notorious for having complex UI's and forms that people need to fill out. At my job we use React and Redux.&lt;/p&gt;

&lt;p&gt;There was a breaking point in 2019 when we needed to make a new UI flow for providers. My co-worker and I were writing out the following Redux object for the &lt;code&gt;nth&lt;/code&gt; time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;didInvalidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;isFetching&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;hasError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;that we realized that there had to be a better way. This pattern wasn't working for us regardless of how many other posts endorsed it. &lt;/p&gt;

&lt;p&gt;Youtube recommended this video on designing state machines for UX:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/VU1NKX6Qkxc"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;and I pitched it to my co-worker the day after. He examined the library and he didn't see any issues with it. We pitched the usage of this library to our engineering manager and he approved it as well.&lt;/p&gt;

&lt;p&gt;It took us longer to build the feature since the xstate API was new to us; but the end result was a bug-free release.&lt;/p&gt;

&lt;p&gt;It wasn't only that using xstate made the release of the feature bug-free, but everything &lt;em&gt;around&lt;/em&gt; writing the code that convinced us that this was the way forward to developing UX.&lt;/p&gt;

&lt;h2&gt;
  
  
  I learned how to ask questions
&lt;/h2&gt;

&lt;p&gt;Finite State Machine models force you as the programmer to clearly state what will happen with the thing you are building. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How does a user get from A -&amp;gt; Z? &lt;/li&gt;
&lt;li&gt;Can the user end up in X for any reason? &lt;/li&gt;
&lt;li&gt;Are there any ways to catch the user if they are in a state we didn't account for?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I never asked myself these questions before coding. These were problems I addressed as I was writing the React components and using &lt;code&gt;this.state&lt;/code&gt; or Redux to figure out. This is backwards.&lt;/p&gt;

&lt;p&gt;We (as programmers for the web) should always be using state charts to answer these questions. I had to ask the designer multiple times about specific error states that weren't addressed. I had to direct message the product manager about the user types that weren't accounted for in the initial feature request. It was an ongoing conversation between all of us because we all wanted the feature to succeed.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I felt like a programmer
&lt;/h2&gt;

&lt;p&gt;We asked questions on what we were building. Once we were experts in the feature that we were about to build, did we then start to write out code.&lt;/p&gt;

&lt;p&gt;As we finalized our machine objects and knew precisely what our feature was going to do, it was this confidence in the finite state machine model that made me feel like we "leveled up".&lt;/p&gt;

&lt;p&gt;We became programmers because we knew how the thing that we built was going to work.&lt;/p&gt;

</description>
      <category>xstate</category>
      <category>finitestatemachines</category>
    </item>
    <item>
      <title>Start to write plugins for OBS with Lua</title>
      <dc:creator>Hector Leiva</dc:creator>
      <pubDate>Thu, 17 Oct 2019 04:32:21 +0000</pubDate>
      <link>https://dev.to/hectorleiva/start-to-write-plugins-for-obs-with-lua-1172</link>
      <guid>https://dev.to/hectorleiva/start-to-write-plugins-for-obs-with-lua-1172</guid>
      <description>&lt;p&gt;I often record my D&amp;amp;D sessions with my friends who are all remote via  OBS. &lt;a href="https://en.wikipedia.org/wiki/Open_Broadcaster_Software" rel="noopener noreferrer"&gt;Open Broadcaster Software&lt;/a&gt; (aka OBS) is a: popular, free, and open-source cross-platform streaming and recording program.&lt;/p&gt;

&lt;p&gt;I have text that is a timestamp of the day we play D&amp;amp;D on the screen so that whenever we review the footage, we always know from which session it was:&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F1ocfwj5p9v5lqv4vp7xu.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F1ocfwj5p9v5lqv4vp7xu.png" alt="Close up Image with Timestamp"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But I didn't want to continue to rewrite the date manually every time.&lt;/p&gt;

&lt;p&gt;Sometimes I would forget and the footage would have the incorrect date and it would affect the order of the videos. So I set out to figure out how to write a script in OBS that would set today's date for me.&lt;/p&gt;

&lt;p&gt;As a bonus, I wanted to see if I could add a &lt;em&gt;prefix&lt;/em&gt; and &lt;em&gt;suffix&lt;/em&gt; options to the date as well, just in case I wanted to add additional text before or after the date.&lt;/p&gt;




&lt;p&gt;OBS is written in C, C++. But for the purposes of this post is to showcase one of OBS' best features which is that since version 21.0+, anyone can write a &lt;code&gt;script&lt;/code&gt; which can use "hooks" that are available in to effectively do anything.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://obsproject.com/docs/scripting.html" rel="noopener noreferrer"&gt;OBS scripting documentation&lt;/a&gt; is located here and there are two important notes. You can write your scripts in either Python 3 or Luajit 2 (Lua 5.2) which hook into the C/C++ methods that run OBS.&lt;/p&gt;

&lt;p&gt;Despite knowing Python more than Lua; this small task which has a fast iterative cycle seemed like the best time to learn how Lua works.&lt;/p&gt;




&lt;h3&gt;
  
  
  Figuring out the OBS hooks for writing plugins:
&lt;/h3&gt;

&lt;p&gt;OBS' main hooks are contained &lt;a href="https://obsproject.com/docs/scripting.html#script-function-exports" rel="noopener noreferrer"&gt;in this document&lt;/a&gt; but I focused on the following hooks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="n"&gt;script_load&lt;/span&gt;
&lt;span class="n"&gt;script_update&lt;/span&gt;
&lt;span class="n"&gt;script_defaults&lt;/span&gt;
&lt;span class="n"&gt;script_properties&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;script_load&lt;/code&gt; in the "lifecycle" of OBS, is called whenever a new source is created or source is activated. Sources are a collection of inputs (audio/video/moving/static) that map to a specific scene.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;script_update&lt;/code&gt; is called whenever the user has modified the script that is in use.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;script_defaults&lt;/code&gt; is called whenever the script is initialized.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;script_properties&lt;/code&gt; is called to load all the possible options for the script.&lt;/p&gt;




&lt;h3&gt;
  
  
  Reverse Engineering a Lua Script
&lt;/h3&gt;

&lt;p&gt;I followed a script that comes preloaded with OBS called &lt;code&gt;countdown.lua&lt;/code&gt; located within &lt;code&gt;frontend-tools/scripts/&lt;/code&gt; and dissected the following things:&lt;/p&gt;

&lt;p&gt;To import the OBS library&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="n"&gt;obs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obslua&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One must set-up global variables&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="n"&gt;source_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="n"&gt;suffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lua is functional, everything else is a function from here on out. To hook into &lt;code&gt;script_properties&lt;/code&gt;, one must make it into a function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;script_properties&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

   &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Specific &lt;code&gt;obs&lt;/code&gt; functions must be called to register the source that we are modifying and the options that that would modify the source. Looking at &lt;code&gt;script_properties&lt;/code&gt; again, I will showcase the script I wrote for this hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="n"&gt;obs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obslua&lt;/span&gt;

&lt;span class="n"&gt;source_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="n"&gt;suffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;script_properties&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;obs_properties_create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;obs_properties_add_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Text Source"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OBS_COMBO_TYPE_EDITABLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OBS_COMBO_FORMAT_STRING&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;sources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;obs_enum_sources&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;-- As long as the sources are not empty, then&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sources&lt;/span&gt; &lt;span class="o"&gt;~=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="c1"&gt;-- iterate over all the sources&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;ipairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="n"&gt;source_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;obs_source_get_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;source_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"text_gdiplus"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;source_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"text_ft2_source"&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
                &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;obs_source_get_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;obs_property_list_add_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&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;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_list_release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;obs_properties_add_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"prefix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Prefix Text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OBS_TEXT_DEFAULT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;obs_properties_add_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"suffix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Suffix Text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OBS_TEXT_DEFAULT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;obs.obs_properties_create()&lt;/code&gt; is used to create an instance of a &lt;code&gt;props&lt;/code&gt; data type that maps to an object in C/C++ for OBS. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Passing &lt;code&gt;props&lt;/code&gt; into &lt;code&gt;obs.obs_properties_add_list(props, "source", "Text Source", obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING)&lt;/code&gt; creates a dropdown list that has a user-facing &lt;code&gt;Text Source&lt;/code&gt; that maps its values to &lt;code&gt;"source"&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The loop that starts here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;sources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;obs_enum_sources&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;-- As long as the sources are not empty, then&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sources&lt;/span&gt; &lt;span class="o"&gt;~=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="c1"&gt;-- iterate over all the sources&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;ipairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is called by loading all the sources that the user has, making sure at least one exists, and then iterating over them. I only want to target sources for which the source is a Text source only. That's what is happening here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;ipairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="n"&gt;source_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;obs_source_get_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;source_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"text_gdiplus"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;source_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"text_ft2_source"&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
                &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;obs_source_get_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;obs_property_list_add_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&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;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the source has an internal id of &lt;code&gt;"text_gdiplus"&lt;/code&gt; or &lt;code&gt;"text_ft2_source"&lt;/code&gt;, we grab the name of that source and modify the properties of the list from earlier and add the &lt;code&gt;name&lt;/code&gt; of that source into the dropdown.&lt;/p&gt;

&lt;h3&gt;
  
  
  Important note, you are still writing in C/C++
&lt;/h3&gt;

&lt;p&gt;Despite the fact that we can write these scripts in Lua or Python, these scripts are linked to the C/C++ internals of OBS and thus follow the same rules of needing to &lt;em&gt;release memory whenever we no longer need the resource&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;That means if we do: &lt;code&gt;local sources = obs.obs_enum_sources()&lt;/code&gt;, after we are done with messing with the &lt;code&gt;sources&lt;/code&gt;, we &lt;em&gt;need&lt;/em&gt; to release this from memory by doing the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_list_release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just because we are using higher level languages in this script, that doesn't mean we can forget about memory management.&lt;/p&gt;

&lt;p&gt;The last part is needing to add an option for the &lt;em&gt;prefix&lt;/em&gt; and &lt;em&gt;suffix&lt;/em&gt; and have it available as a free-text field for the user to fill in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;    &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;obs_properties_add_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"prefix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Prefix Text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OBS_TEXT_DEFAULT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;obs_properties_add_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"suffix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Suffix Text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OBS_TEXT_DEFAULT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we are done with setting up the configuration for the script, we need to return the &lt;code&gt;props&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fv9c31hr0zrjuubsws837.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fv9c31hr0zrjuubsws837.png" alt="Loaded Script"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;The other 3 "hooks" for this implementation can be viewed at my Github page where I've made this code available for anyone else to use:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hectorleiva/obs_current_date" rel="noopener noreferrer"&gt;https://github.com/hectorleiva/obs_current_date&lt;/a&gt;&lt;/p&gt;

</description>
      <category>lua</category>
      <category>obs</category>
    </item>
  </channel>
</rss>
