<?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: Christian Engel</title>
    <description>The latest articles on DEV Community by Christian Engel (@paratron).</description>
    <link>https://dev.to/paratron</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%2F851333%2F1359b304-bdfa-46db-ae52-f5f14a510dec.jpg</url>
      <title>DEV Community: Christian Engel</title>
      <link>https://dev.to/paratron</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/paratron"/>
    <language>en</language>
    <item>
      <title>Advanced LLM Prompting Techniques: A Guide for Software Engineers</title>
      <dc:creator>Christian Engel</dc:creator>
      <pubDate>Fri, 27 Sep 2024 20:32:24 +0000</pubDate>
      <link>https://dev.to/paratron/advanced-llm-prompting-techniques-a-guide-for-software-engineers-22b6</link>
      <guid>https://dev.to/paratron/advanced-llm-prompting-techniques-a-guide-for-software-engineers-22b6</guid>
      <description>&lt;p&gt;Hello Dev.to community,&lt;br&gt;
I've recently published an article that may be of interest to software engineers working with large language models (LLMs) like GPT-4 and Claude 3.5. The guide covers advanced prompting techniques that can enhance the effectiveness of LLMs in various development scenarios.&lt;/p&gt;

&lt;p&gt;Topics Covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chain-of-Thought (CoT) Prompting&lt;/li&gt;
&lt;li&gt;Few-Shot and Zero-Shot Techniques&lt;/li&gt;
&lt;li&gt;Self-Consistency Prompting&lt;/li&gt;
&lt;li&gt;Role-Playing Prompts&lt;/li&gt;
&lt;li&gt;Contextual Prompting&lt;/li&gt;
&lt;li&gt;Tree of Thought (ToT)&lt;/li&gt;
&lt;li&gt;ReAct Framework&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The article provides practical examples and compares the effectiveness of these techniques when applied to ChatGPT and Claude. It aims to help developers:&lt;/p&gt;

&lt;p&gt;Improve problem-solving capabilities in AI-assisted coding&lt;br&gt;
Optimize LLM performance for specific development tasks&lt;br&gt;
Implement more sophisticated reasoning in AI-powered applications&lt;/p&gt;

&lt;p&gt;If you're interested in exploring how these techniques can be applied in software development, you can find the full article here: &lt;a href="https://parastudios.de/advanced-prompting-techniques-llm-guide/" rel="noopener noreferrer"&gt;Advanced Prompting Techniques for Modern Large Language Models&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd be interested to hear about your experiences with LLMs in software development. Have you used any of these techniques in your projects? What challenges have you encountered when working with AI language models?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>development</category>
    </item>
    <item>
      <title>PLANSEARCH: Improving reasoning for LLMs</title>
      <dc:creator>Christian Engel</dc:creator>
      <pubDate>Tue, 24 Sep 2024 15:37:15 +0000</pubDate>
      <link>https://dev.to/paratron/plansearch-improving-reasoning-for-llms-p76</link>
      <guid>https://dev.to/paratron/plansearch-improving-reasoning-for-llms-p76</guid>
      <description>&lt;p&gt;Hey devs! 👋 I've just published a blog post analyzing PLANSEARCH, a groundbreaking approach to AI problem-solving in coding. Here's why you should care:&lt;/p&gt;

&lt;p&gt;Breaks down problem-solving into stages: observe, combine insights, develop solutions, implement&lt;br&gt;
Significantly improves code generation task performance&lt;br&gt;
Boosts solve rates from 40% to 70%+ on coding benchmarks&lt;br&gt;
Potential to enhance developer productivity on complex tasks&lt;/p&gt;

&lt;p&gt;In my full post, I dive into:&lt;/p&gt;

&lt;p&gt;How PLANSEARCH works&lt;br&gt;
Comparisons with other AI approaches&lt;br&gt;
Potential applications in software development&lt;br&gt;
The future of AI-assisted coding&lt;/p&gt;

&lt;p&gt;Curious? Check out the full article at &lt;a href="https://parastudios.de/paper-review-plansearch/" rel="noopener noreferrer"&gt;https://parastudios.de/paper-review-plansearch/&lt;/a&gt; . Let's discuss - have you used AI-assisted coding tools?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>programming</category>
      <category>news</category>
    </item>
    <item>
      <title>🛑 Stop Coding Already! 🛑</title>
      <dc:creator>Christian Engel</dc:creator>
      <pubDate>Mon, 23 Sep 2024 20:50:12 +0000</pubDate>
      <link>https://dev.to/paratron/stop-coding-already-44m7</link>
      <guid>https://dev.to/paratron/stop-coding-already-44m7</guid>
      <description>&lt;p&gt;As developers, we often can't wait to start coding when inspiration strikes. But what if I told you that not coding might be the key to better projects?&lt;/p&gt;

&lt;p&gt;Here's why you should stop coding (for a bit):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your mind is faster than your keyboard. Mental modeling can save you from weeks of frustration.&lt;/li&gt;
&lt;li&gt;"Mental coding" happens anywhere. Shower thoughts could be your next breakthrough.&lt;/li&gt;
&lt;li&gt;Tools like Obsidian turn ideas into plans. Organize your thoughts before you organize your code.&lt;/li&gt;
&lt;li&gt;AI can be your devil's advocate. Use LLMs to challenge your assumptions.&lt;/li&gt;
&lt;li&gt;Think MVP, then iterate. Plan small, start small, grow smart.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next time inspiration hits, close that IDE. Plan first, code second.&lt;/p&gt;

&lt;p&gt;What's your take? Do you plan extensively, or do you prefer to dive right in? Share your approach below!&lt;/p&gt;

&lt;p&gt;Read the full article here: &lt;a href="https://parastudios.de/stop-coding-already/" rel="noopener noreferrer"&gt;https://parastudios.de/stop-coding-already/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>programming</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>🚀 Enhancing OAuth Security &amp; User Experience 🔒</title>
      <dc:creator>Christian Engel</dc:creator>
      <pubDate>Mon, 23 Sep 2024 07:10:34 +0000</pubDate>
      <link>https://dev.to/paratron/enhancing-oauth-security-user-experience-46a0</link>
      <guid>https://dev.to/paratron/enhancing-oauth-security-user-experience-46a0</guid>
      <description>&lt;p&gt;In my recent blog post, I explore how to integrate OAuth into a web app (SvelteKit, in my example), ensuring both top-notch security and a seamless user experience. I cover:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🛡️ Utilizing the OAuth state parameter
🔑 Strengthening CSRF protection with httpOnly cookies
🔄 Handling login vs. registration flows effectively
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you're looking to optimize OAuth in your web app, check out the full guide!&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://parastudios.de/maximizing-security-user-experience-oauth-sveltekit/" rel="noopener noreferrer"&gt;Read the full post here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>security</category>
      <category>ux</category>
    </item>
    <item>
      <title>Hello world, this is RoboStreamer</title>
      <dc:creator>Christian Engel</dc:creator>
      <pubDate>Wed, 25 May 2022 10:42:48 +0000</pubDate>
      <link>https://dev.to/paratron/hello-world-this-is-robostreamer-35n9</link>
      <guid>https://dev.to/paratron/hello-world-this-is-robostreamer-35n9</guid>
      <description>&lt;p&gt;Hey DEV community!&lt;br&gt;
I wanted to share a project I am working on in my free time and tell a bit about how it's structured and which frameworks and/or languages I picked to develop it.&lt;/p&gt;

&lt;p&gt;The project is called &lt;a href="https://robostreamer.com" rel="noopener noreferrer"&gt;RoboStreamer&lt;/a&gt; and allows users to upload pre-recorded video files to have them streamed via RMTP to their Steam Store pages. So its basically a marketing tool for (indie) game developers and publishers.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Everything&lt;/em&gt; in this project is done by myself - the planning, development, design and marketing. I am happy to answer any questions you have!&lt;/p&gt;

&lt;h2&gt;
  
  
  About me
&lt;/h2&gt;

&lt;p&gt;My name is Chris - I am living in germany and currently working as &lt;a href="https://www.linkedin.com/in/christian-engel-a73457236/?lipi=urn%3Ali%3Apage%3Ad_flagship3_feed%3B0DgOOlQsR7WT7DAq5WAUmg%3D%3D" rel="noopener noreferrer"&gt;Head of Datadriven Content Portals&lt;/a&gt; for t-online.de - one of, if not &lt;em&gt;the&lt;/em&gt; biggest german news site with more than 47 million monthly active users.&lt;/p&gt;

&lt;p&gt;My team is responsible for concept/planning, development and delivery of the web portals which deliver live stock market and sports data.&lt;/p&gt;

&lt;p&gt;I am an active web developer for nearly 20 years now and worked on lots of medium and big projects, try to be helpful for other devs and in &lt;a href="https://github.com/paratron" rel="noopener noreferrer"&gt;the open source community&lt;/a&gt; and even founded a startup once.&lt;/p&gt;

&lt;p&gt;In my free time, I work on different pet projects and one of them is RoboStreamer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I started working on RoboStreamer
&lt;/h2&gt;

&lt;p&gt;Out of personal interest, I have a closer eye on the indie point-and-click adventure bubble. In my childhood, I loved playing games like Monkey Island, Day of the Tentacle, Kings' Quest and others.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3o586bjp5kywn516zyfg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3o586bjp5kywn516zyfg.jpg" alt="Four screenshots of the games 'Curse of Monkey Island', 'Day of the Tentacle', 'Kings Quest VII' and 'Indiana Jones and the Fate of Atlantis'" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Screenshots of the games 'Curse of Monkey Island', 'Day of the Tentacle', 'Kings Quest VII' and 'Indiana Jones and the Fate of Atlantis'&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;During a recent &lt;a href="https://bigadventureevent.com/" rel="noopener noreferrer"&gt;Adventure Game Steam Event&lt;/a&gt;, I noticed some developers were hosting pre-recorded Steam Broadcasts. I researched a bit about how this is done and noticed that its surprisingly much work to do so. You not only have to produce your video but you need to leave your computer running and follow strict configuration parameters in order to make your video appear as a live stream on your Steam Store Page.&lt;/p&gt;

&lt;p&gt;And there are lots of developers doing this because - totally understandable - they have games to develop.&lt;/p&gt;

&lt;p&gt;I noticed that - when you know how to do it, it's relatively simple to let an instance of ffmpeg handle the streaming part. For initial tests, I had a webserver running, installed &lt;a href="https://ffmpeg.org/" rel="noopener noreferrer"&gt;ffmpeg&lt;/a&gt; there and configured the streaming settings by hand. I did so in order to support an indie game developer friend of mine.&lt;/p&gt;

&lt;p&gt;This worked surprisingly well, and I thought by myself: if I add a simple UI around it, I may be able to help a lot of indie devs out there!&lt;/p&gt;

&lt;h2&gt;
  
  
  How RoboStreamer works
&lt;/h2&gt;

&lt;p&gt;This is my first personal project I built in a decentralized manner where even parts of the main system are separated processes being able to run on separate servers. For the sake of development speed, I settled with &lt;a href="https://strapi.io" rel="noopener noreferrer"&gt;Strapi&lt;/a&gt; as my main data storage solution. It gives me an API and user authentication without much hassle. And from Strapi, its relatively straightforward piping events to my other services.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F09n6ufsj4dn92lxusomk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F09n6ufsj4dn92lxusomk.jpg" alt="A diagram showcasing the different parts of the RoboStreamer system" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A diagram showcasing the different parts of the RoboStreamer system&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is the upload service which accepts large video files cut into small pieces. It's responsible for merging the pieces back together and moves them to a storage solution hosted with Hetzner.&lt;br&gt;
I will add video conversion there as well to make it even easier to create Steam Broadcasts from pre-recorded videos.&lt;/p&gt;

&lt;p&gt;Another service is the coordination process which will spread streaming jobs to multiple running streamer processes. The&lt;br&gt;
coordination process is like a load balancer for the streaming requests - it will give jobs to streamer instances with free capacity or even boot up new streamer instances, when needed.&lt;/p&gt;

&lt;p&gt;All of those services are written in &lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;Typescript&lt;/a&gt; and run via &lt;a href="https://nodejs.org" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, there is the frontend process powering the RoboStreamer website and the control center. The whole frontend is created with &lt;a href="https://svelte.dev" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt; using &lt;a href="https://elderguide.com/tech/elderjs/" rel="noopener noreferrer"&gt;Elder.js&lt;/a&gt; which makes it easy to create fast and SEO friendly web projects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F911ik5vuilel2nzcw2kn.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F911ik5vuilel2nzcw2kn.jpg" alt="Screnshot of the RoboStreamer Controlcenter with cursor hovering over a 'Start Stream' button" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Partial screenshot of the RoboStreamer controlcenter&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Thanks for reading!
&lt;/h2&gt;

&lt;p&gt;If you are interested in using RoboStreamer for your Steam Store Page, give me a shout on &lt;a href="https://twitter.com/therobostreamer" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or register for the newsletter on the homepage.&lt;/p&gt;

&lt;p&gt;If you'd like to discuss techy things - you are very welcome to have a chat with me as well!&lt;/p&gt;

&lt;p&gt;Greetings, Chris&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>svelte</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Limit access of Strapi users to their own entries</title>
      <dc:creator>Christian Engel</dc:creator>
      <pubDate>Sun, 15 May 2022 19:40:56 +0000</pubDate>
      <link>https://dev.to/paratron/limit-access-of-strapi-users-to-their-own-entries-298l</link>
      <guid>https://dev.to/paratron/limit-access-of-strapi-users-to-their-own-entries-298l</guid>
      <description>&lt;p&gt;This was actually one of the first problems I encountered when I began using Strapi version 4 as a backend boilerplate for my &lt;a href="https://robostreamer.com" rel="noopener noreferrer"&gt;streaming service for steam broadcasts&lt;/a&gt; side project. &lt;/p&gt;

&lt;p&gt;What I wanted was that when a user requested a list of or a certain entity, the backend should only respond with entities which belong to the user. By default, Strapi would send all entities from everyone.&lt;/p&gt;

&lt;p&gt;Lets say you create a TODO app with a &lt;code&gt;/tasks&lt;/code&gt; endpoint. Each user should be able to create tasks by doing &lt;code&gt;POST&lt;/code&gt; requests but if they do a &lt;code&gt;GET&lt;/code&gt; request to &lt;code&gt;/tasks&lt;/code&gt; they should &lt;em&gt;not&lt;/em&gt; see everyones tasks. Also &lt;code&gt;PUT&lt;/code&gt; requests should only work when the given task belongs to the user.&lt;/p&gt;

&lt;p&gt;So how do we achieve this?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Use a service call in the &lt;code&gt;create()&lt;/code&gt; method to set the &lt;code&gt;owner&lt;/code&gt; field with system privileges.&lt;br&gt;
Force a filter to the id of the current user in all other methods.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Extend the Strapi controller
&lt;/h2&gt;

&lt;p&gt;Lets stay with the &lt;code&gt;tasks&lt;/code&gt; example for this article. The moment you create that data type through the Strapi Content-Type Builder, Strapi will create a dummy controller in &lt;code&gt;/src/api/task/controllers/task.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The content will look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 *  task controller
 */&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;createCoreController&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strapi/strapi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;factories&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createCoreController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;api::task.task&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;In order to get the behavior we want to achieve, we need to roll our own &lt;code&gt;find()&lt;/code&gt;, &lt;code&gt;findOne()&lt;/code&gt;, &lt;code&gt;update()&lt;/code&gt; and &lt;code&gt;delete()&lt;/code&gt; methods to limit the access. The &lt;code&gt;create()&lt;/code&gt; route needs to be defined as well, since - as far as I know - Strapi is unable to link the current user to an entry automatically.&lt;/p&gt;

&lt;p&gt;So lets begin with saving the creator of a task in its own field. I added a new field in the Strapi Content-Type Builder which is a relation to the users table. Yes, Strapi will keep track of the user which was the creator of an entry but this is not editable. If you as an administrator create a task for a user in the admin interface, your name will be set as the creator and you can't do anything to change that. So we create a "real field" on the entry.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsauhkmv4uu2l7lrf2c6a.jpg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsauhkmv4uu2l7lrf2c6a.jpg" alt="Strapi relation field dialog"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;create()&lt;/code&gt; method
&lt;/h3&gt;

&lt;p&gt;Now lets update the &lt;code&gt;task&lt;/code&gt; controller so upon creation the &lt;code&gt;owner&lt;/code&gt; field will be set to the id of the user which issued the request.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 *  task controller
 */&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;createCoreController&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strapi/strapi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;factories&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createCoreController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;api::task.task&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;async&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&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="nx"&gt;user&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;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&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;updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;strapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entityService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;api::task.task&lt;/span&gt;&lt;span class="dl"&gt;"&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="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="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&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="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&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;With this code in place - whenever a user creates a new task via http &lt;code&gt;POST&lt;/code&gt;, the controller will read the user object from the request state (a bit like a session), create a task and then immediately update it by setting the owner field to the user id.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why the two step process?
&lt;/h4&gt;

&lt;p&gt;The immediate thought I would have when reading the above code would be: why not sneaking the owner id into the payload that is passed to &lt;code&gt;super.create()&lt;/code&gt; and avoiding the separete call to &lt;code&gt;update()&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Well, the reason is that this will only work when the issuing user has access to modify the &lt;code&gt;users&lt;/code&gt; table. If not, there is no way Strapi will let you set that value. So by calling &lt;code&gt;super.create(ctx)&lt;/code&gt; first, we make sure the task entry is created in the name of the user.&lt;/p&gt;

&lt;p&gt;Our next call to &lt;code&gt;strapi.entityService.update()&lt;/code&gt; is done with system privileges, tough. The action is not related to a user so no permissions will be checked. The owner field will be set with super admin privileges (so be &lt;em&gt;extra careful&lt;/em&gt; when doing this).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads up!&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The automatic owner assignment will &lt;em&gt;not work&lt;/em&gt; when you create tasks inside the admin interface. Controller methods are only being called when requests are made through the public REST or GraphQL api.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;find()&lt;/code&gt; method
&lt;/h3&gt;

&lt;p&gt;When the tasks application issues a &lt;code&gt;GET&lt;/code&gt; request to &lt;code&gt;/tasks&lt;/code&gt;, Strapi will return a list of &lt;em&gt;all&lt;/em&gt; existing tasks, of &lt;em&gt;all&lt;/em&gt; users. To prevent that, we need to forcefully add a filter to the query.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&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="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filters&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="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
        &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&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;Again, we get the current user object from the request state. Then we update the &lt;code&gt;filters&lt;/code&gt; property of the &lt;code&gt;query&lt;/code&gt; object for the request. We copy over all filters which have been passed by the user, then we set the &lt;code&gt;owner&lt;/code&gt; field to the current users &lt;code&gt;id&lt;/code&gt;. This way, the filter is always set serverside, even if the user would pass in a forged &lt;code&gt;owner&lt;/code&gt; parameter from the outside. It would not be possible to request tasks of a different user.&lt;/p&gt;

&lt;h3&gt;
  
  
  The other methods
&lt;/h3&gt;

&lt;p&gt;Since wrapping the other methods in the same filter is basically copy/paste, I will put them all together, here:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&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="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filters&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="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
        &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&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="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&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="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filters&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="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
        &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&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="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&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="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filters&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="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
        &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&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;You may want to DRY your code a bit, but I actually copy/pasted the parts for real to be able to make other customizations more quickly.&lt;/p&gt;




&lt;p&gt;This post was published on my &lt;a href="https://parastudios.de/" rel="noopener noreferrer"&gt;personal blog&lt;/a&gt;, first. &lt;/p&gt;

&lt;p&gt;You should follow me on dev.to for more tips like this. Click the follow button in the sidebar! 🚀&lt;/p&gt;

</description>
      <category>strapi</category>
      <category>javascript</category>
      <category>api</category>
      <category>backend</category>
    </item>
    <item>
      <title>Strapi quirks you should know about</title>
      <dc:creator>Christian Engel</dc:creator>
      <pubDate>Sat, 30 Apr 2022 21:07:47 +0000</pubDate>
      <link>https://dev.to/paratron/strapi-quirks-you-should-know-about-4ie4</link>
      <guid>https://dev.to/paratron/strapi-quirks-you-should-know-about-4ie4</guid>
      <description>&lt;p&gt;When I first came across Strapi, I had some misconceptions about it which are not immediately clear when reading about the framework in their docs.&lt;/p&gt;

&lt;p&gt;I learned about some concepts and approaches only when actively using Strapi and wished to have known them before.&lt;/p&gt;

&lt;p&gt;This is not a rant but just a heads up for other devs who consider building a project with strapi. I hope I can save them some hours of looking around in the docs and pulling out some hair. If it sounds ranty in some places, its because I experienced lots of frustration sometimes, so bear with me.&lt;/p&gt;

&lt;p&gt;Here we go:&lt;/p&gt;

&lt;h2&gt;
  
  
  The admin UI skips all business logic implemented into the API
&lt;/h2&gt;

&lt;p&gt;This assumption threw me off the most because it leads to a chain of conclusions of what should be possible. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For me, Strapi looked like a framework that lets you define a CRUD API, handles user authentication and offers an admin UI to interact with that API without having to build a dedicated UI first.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Thats only halfway true. Yes, Strapi allows to define a data model and creates API endpoints for that model. It also allows to pour business logic into the mix by offering the ability to create new or modify existing endpoints (routes), controllers, services and more.&lt;/p&gt;

&lt;p&gt;You begin to scratch your head when all of those modifications are completely ignored by the admin UI. The reason is: Strapi creates &lt;em&gt;two&lt;/em&gt; APIs. One I call the "frontend API" which is being consumed by your application and one "admin API" which is used exclusively by the admin UI.&lt;/p&gt;

&lt;p&gt;There should be a big, red warning right on the getting started page: &lt;strong&gt;"Strapi Admin does in &lt;em&gt;no way&lt;/em&gt; interact with the Strapi API"&lt;/strong&gt;_&lt;/p&gt;

&lt;p&gt;The problem is: when you add business logic like calling external services for additional data or validation, notify external systems or automatically adding rows into other collections, you are lost with the admin UI. You can manipulate the database contents but its not at all aware of the logic you added to the frontend API.&lt;/p&gt;

&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; hack yourself into the admin API but there is nearly no documentation about it. I ended up reading lots of Strapi source code on github to learn about how the admin API works.&lt;/p&gt;

&lt;h2&gt;
  
  
  There are users and then there are users
&lt;/h2&gt;

&lt;p&gt;Another design decision in Strapi was to have two kinds of users. One can access the frontend API only and others can access only the admin API. I assumed first that any user could have extra privileges given to access the admin UI. This is not the case.&lt;/p&gt;

&lt;p&gt;Bonus points to make things worse: e-mail adresses need to be unique among both kinds of users. If there ist a user with the mail &lt;a href="mailto:test@example.com"&gt;test@example.com&lt;/a&gt; for the frontend API, you cannot create another for the admin UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ralations to the users table are complicated
&lt;/h2&gt;

&lt;p&gt;No pun intended.&lt;/p&gt;

&lt;p&gt;I like to give my collections an &lt;code&gt;owner&lt;/code&gt; field with a relation to the users table of the users and permissions plugin. This way I can easily connect objects (posts, comments, tasks, whatever) to their owners. That connection might be altered through business logic as well.&lt;/p&gt;

&lt;p&gt;However, in order to set or read the relation, the requesting user needs to have full access to the users table. This means they would be able to pull user data from the API as well.&lt;/p&gt;

&lt;p&gt;I work around this problem by setting and populating the owner fields separately through direct service calls in my controllers. This add one additional query to the database tough.&lt;/p&gt;

</description>
      <category>strapi</category>
      <category>javascript</category>
      <category>backend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Mocking modules with jest and typescript</title>
      <dc:creator>Christian Engel</dc:creator>
      <pubDate>Fri, 29 Apr 2022 06:00:10 +0000</pubDate>
      <link>https://dev.to/paratron/mocking-modules-with-jest-and-typescript-3kl9</link>
      <guid>https://dev.to/paratron/mocking-modules-with-jest-and-typescript-3kl9</guid>
      <description>&lt;p&gt;Since it took me some time to understand the behaviour and the jest docs don't tell much about it:&lt;/p&gt;

&lt;p&gt;I want to mock&lt;br&gt;
modules with jest and especially update the return values of module functions across my tests.&lt;/p&gt;

&lt;p&gt;Consider you want to test a function called &lt;code&gt;someFunctionToTest&lt;/code&gt; from a module called &lt;code&gt;moduleA&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;someFunctionToTest&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;moduleA&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;It does something&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;someFunctionToTest&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;something&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;Now you notice that the function relies on calling the method &lt;code&gt;helper&lt;/code&gt; from &lt;code&gt;moduleB&lt;/code&gt;. You want to mock that function&lt;br&gt;
and simulate a return value:&lt;/p&gt;
&lt;h2&gt;
  
  
  Variant 1
&lt;/h2&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;someFunctionToTest&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;moduleA&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;helper&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;moduleB&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;moduleB&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;It does something&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="o"&gt;=&amp;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;helper&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;someFunctionToTest&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;something&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;It does something else&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="o"&gt;=&amp;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;helper&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;someFunctionToTest&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;something&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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 trick - and its a bit counter-intuitive - is to import the mocked function as well. See line 2 where we import the &lt;code&gt;helper&lt;/code&gt; method&lt;br&gt;
into our test file. This is actually the mock function. The reason is that jest does some code shifting and altough the call to &lt;code&gt;jest.mock&lt;/code&gt; &lt;br&gt;
occures later, it will be executed before any imports are made. Therefore, &lt;code&gt;helper&lt;/code&gt; is an instance of &lt;code&gt;jest.fn()&lt;/code&gt; in our test file.&lt;/p&gt;

&lt;p&gt;Now we can update the return values of our mocked function by calling &lt;code&gt;mockedFunction.mockReturnValue()&lt;/code&gt;. There is only one catch: typescript&lt;br&gt;
will complain that the imported &lt;code&gt;helper&lt;/code&gt; has no function &lt;code&gt;mockReturnValue&lt;/code&gt; to be called. Thats because typescript does not know that &lt;code&gt;helper&lt;/code&gt;&lt;br&gt;
is not the real thing but a mocked function. We can get around it by casting the type locally to &lt;code&gt;jest.Mock&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Variant 2
&lt;/h2&gt;

&lt;p&gt;After working some time with the variant 1, I came up with a variant 2 that may be more effective when you write lots of tests:&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;someFunctionToTest&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;moduleA&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;helper&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;helperOriginal&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;moduleB&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;helper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;helperOriginal&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;moduleB&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;It does something&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;helper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;someFunctionToTest&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;something&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;It does something else&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;helper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;someFunctionToTest&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;something&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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 way, the &lt;code&gt;helper&lt;/code&gt; method is not directly imported but imported as an alternatively named variable and then re-assigned and&lt;br&gt;
casted to type &lt;code&gt;jest.Mock&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;There is also a third variant (if your linter allows you to use &lt;code&gt;require()&lt;/code&gt;):&lt;/p&gt;

&lt;h2&gt;
  
  
  Variant 3
&lt;/h2&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;someFunctionToTest&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;moduleA&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;helper&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;moduleB&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;helper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Mock&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;moduleB&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;It does something&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;helper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;someFunctionToTest&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;something&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;It does something else&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;helper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;someFunctionToTest&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;something&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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;Pick whatever suits you the best. :)&lt;/p&gt;




&lt;p&gt;This post was published on my &lt;a href="https://parastudios.de/" rel="noopener noreferrer"&gt;personal blog&lt;/a&gt;, first. &lt;/p&gt;

&lt;p&gt;You should follow me on dev.to for more tips like this. Click the follow button in the sidebar! 🚀&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>testing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Introducing and using Obsidian</title>
      <dc:creator>Christian Engel</dc:creator>
      <pubDate>Sun, 24 Apr 2022 07:37:23 +0000</pubDate>
      <link>https://dev.to/paratron/introducing-and-using-obsidian-3o17</link>
      <guid>https://dev.to/paratron/introducing-and-using-obsidian-3o17</guid>
      <description>&lt;p&gt;I recently stumbled across a free app I never heard of before: &lt;a href="https://obsidian.md" rel="noopener noreferrer"&gt;Obsidian&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Its an app for writing and organizing markdown text documents. While this does not sound like much, it changed my work life a lot and gave me such a positive impact that I wrote this post to share the app with you.&lt;/p&gt;

&lt;h3&gt;
  
  
  My struggle with taking notes
&lt;/h3&gt;

&lt;p&gt;I read and learned about the art of note-taking before. I know that some sophisticated people keep huge collections of notes, thoughts and pieces of wisdom - for example in systems like the &lt;a href="https://en.wikipedia.org/wiki/Zettelkasten" rel="noopener noreferrer"&gt;Zettelkasten&lt;/a&gt; which was already used by Jules Verne. I also read a story about a businessman some time ago who made a note with details about every person he ever met so he can look them up again when he would meet them again, sometimes after years.&lt;/p&gt;

&lt;p&gt;While I always kept a classic note book in paper form near me, I only rarely used it. I am a very digital person - always working on lots of projects. Either for my employee or for myself. A note book is a linear medium. You fill page by page untill there is no more space. But my thoughts wander around. I sketch out an idea halfway through, then work on something else. Or I add more information to some topic later on. Thats difficult with a book. I always tried to leave some pages blank between unrelated topics, but it always gave me a feeling of limited space to express my thoughts. Also its not searchable. And not linkable. And I can't use it in the dark.&lt;/p&gt;

&lt;h3&gt;
  
  
  What makes obsidian valuable for me
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The obsidian app is being installed on your device - be it a Windows, Mac or Linux desktop or an Android or iOS device - it works anywhere.&lt;/li&gt;
&lt;li&gt;It creates a local folder with text files in it. Completely offline and completely yours. No cloud used, no account needed.&lt;/li&gt;
&lt;li&gt;The texts can be formatted with markdown - complete with links and images.&lt;/li&gt;
&lt;li&gt;You can go ahead and add or edit the text files in the folder with any other text editor.&lt;/li&gt;
&lt;li&gt;It formats code blocks with beautiful syntax highlighting.&lt;/li&gt;
&lt;li&gt;It can export your formatted texts as PDFs.&lt;/li&gt;
&lt;li&gt;Writing texts in dark mode on my mobile is pure bliss.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  On steroids with device sync
&lt;/h3&gt;

&lt;p&gt;The app itself offers a paid sync feature for $4/mo (as the time of this writing). Since the app writes plain text to a folder on your device, you get some other options tough.&lt;/p&gt;

&lt;p&gt;I went ahead with &lt;a href="https://syncthing.net/" rel="noopener noreferrer"&gt;Syncthing&lt;/a&gt;, an open source file sync tool for Windows, MacOS, Linux and Android. Since it also works without a cloud or an account, it was the perfect solution for me. Apps like Dropbox or Google Drive dont sync a folder on your mobile and dont work in this case.&lt;/p&gt;

&lt;p&gt;Setting it up on android was a bit tricky because of the security constraints but I eventually figured out how to make it work: the "Documents" Folder on my android device can be read/write accessed by any app with permission, so I set the Obsidian Vault location there. I then shared the vault folder through syncthing with my laptop.&lt;/p&gt;

&lt;p&gt;Now, whenever I take notes on my mobile, I can pick them up on my laptop. Or when I write down extensive research on my laptop, I can browse and search it on my mobile when I'm on the run.&lt;/p&gt;

&lt;p&gt;And since everything is markdown based, I even wrote this blog post in Obsidian. What a great and truly open piece of software!&lt;/p&gt;

&lt;p&gt;I just started using obsidian in my everyday work and it already had a big impact on my workflow. I strongly suggest you to give it a try.&lt;/p&gt;




&lt;p&gt;This post was published on my &lt;a href="https://parastudios.de/" rel="noopener noreferrer"&gt;personal blog&lt;/a&gt;, first. &lt;/p&gt;

&lt;p&gt;You should follow me on dev.to for more tips like this. Click the follow button in the sidebar! 🚀&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>A typed event system for Typescript</title>
      <dc:creator>Christian Engel</dc:creator>
      <pubDate>Thu, 21 Apr 2022 16:42:51 +0000</pubDate>
      <link>https://dev.to/paratron/a-typed-event-system-for-typescript-25pb</link>
      <guid>https://dev.to/paratron/a-typed-event-system-for-typescript-25pb</guid>
      <description>&lt;p&gt;I am working on a &lt;a href="https://robostreamer.com" rel="noopener noreferrer"&gt;pet project&lt;/a&gt; with distributed services right now.&lt;/p&gt;

&lt;p&gt;During the development, I was in need of a classic on/off style event system which is usually quickly written with &lt;br&gt;
a couple lines of code. This time however, I wondered if it would be possible to have the whole event system type guarded&lt;br&gt;
with Typescript types.&lt;/p&gt;
&lt;h2&gt;
  
  
  Get the code
&lt;/h2&gt;

&lt;p&gt;I stored the whole 58 lines of code in &lt;a href="https://gist.github.com/Paratron/faacf00bc9a06d8adce1220c6a65f041" rel="noopener noreferrer"&gt;a github gist&lt;/a&gt;. Please be aware that I don't take any responsibility if the code&lt;br&gt;
does not work as intented or causes failures somewhere. Use it at your own risk.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to use it
&lt;/h2&gt;

&lt;p&gt;If you have ever worked with a on/off style event emitter, the usage of this system will be straight forward. I am still going&lt;br&gt;
to explain it shortly.&lt;/p&gt;
&lt;h3&gt;
  
  
  How a basic on/off style event system works
&lt;/h3&gt;

&lt;p&gt;You may skip to the next headline, if you know about this already :)&lt;/p&gt;

&lt;p&gt;For an event system, you usually have points in your code which trigger events and broadcast the occurance of the events without&lt;br&gt;
knowing if someone actually listens for them.&lt;/p&gt;

&lt;p&gt;Other parts of your code may subscribe to the existing events and react on them. If you ever worked with events in the DOM of a website,&lt;br&gt;
its exactly this pattern:&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;// Adding a callback function to "change" events on a text box.&lt;/span&gt;
&lt;span class="c1"&gt;// The callback is called every time when a change event happens.&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;myCoolTextbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;change&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="c1"&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 there may be infinite subscribers (listeners) to some event and whenever it gets triggered, all subscribers will be notified. &lt;/p&gt;

&lt;h3&gt;
  
  
  Prepare the types of your events
&lt;/h3&gt;

&lt;p&gt;I designed the event system so you can create a typescript &lt;code&gt;interface&lt;/code&gt; to define all your existing events as well as their data payloads.&lt;/p&gt;

&lt;p&gt;If we for example want to create a fictional video player app with an event system, we could define the following events:&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AppEvents&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;load&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;filename&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;format&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;totalTimeMs&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="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;currentTimeMs&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="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;play&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we have a &lt;code&gt;load&lt;/code&gt; event when the user opens a file, an &lt;code&gt;update&lt;/code&gt; event which fires continuously while playing back the file&lt;br&gt;
and &lt;code&gt;play&lt;/code&gt;, &lt;code&gt;pause&lt;/code&gt; and &lt;code&gt;stop&lt;/code&gt; events that are triggered upon user interaction.&lt;/p&gt;
&lt;h3&gt;
  
  
  Creating and using the event system
&lt;/h3&gt;

&lt;p&gt;With our typescript interface at hand defining all events in our system, we can actually create the system in code:&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;// appEvents.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;createEventSystem&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;./eventSystem.ts&lt;/span&gt;&lt;span class="dl"&gt;"&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;AppEvents&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;load&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;filename&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;format&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;totalTimeMs&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="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;currentTimeMs&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="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;play&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&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;eventSystem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createEventSystem&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppEvents&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="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;eventSystem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the system can be used from anywhere in our code. Typescript takes care that only known events can be triggered&lt;br&gt;
and they need to receive exactly the expected payloads.&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="nx"&gt;appEvents&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;./appEvents.ts&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;loadAndParseAudioFile&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;./audioLoader.ts&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filename&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;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;totalTimeMs&lt;/span&gt;
    &lt;span class="p"&gt;}&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;loadAndParseAudioFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;appEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;load&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;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;totalTimeMs&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;On some other part of your application, your code may subscribe to the defined events:&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;// logger.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;appEvents&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;./appEvents.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;appEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;load&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;payload&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;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="s2"&gt;`You opened the file &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;filename&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; with a length of &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;totalTimeMs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; milliseconds.`&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;h3&gt;
  
  
  Stop listening to an event
&lt;/h3&gt;

&lt;p&gt;To detach from receiving events, one needs to call the &lt;code&gt;.off()&lt;/code&gt; method and pass the exactly same callback function to the&lt;br&gt;
off handler:&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;handler&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;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An update happened!&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;eventSystem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// And somewhere else:&lt;/span&gt;
&lt;span class="nx"&gt;eventSystem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;off&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please be aware that it needs to be the &lt;em&gt;same&lt;/em&gt; callback function and not another one, with similar code.&lt;/p&gt;

&lt;h3&gt;
  
  
  THIS WONT WORK!
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;eventSystem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;update&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;data&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;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An update happened!&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;eventSystem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;off&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;update&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;data&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;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An update happened!&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// These are TWO separate functions, therefore the "off" call did not work.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using ENUMs as event names
&lt;/h2&gt;

&lt;p&gt;Since typescript interfaces do not care about the format of field names, you are free to define an ENUM to keep your&lt;br&gt;
existing event names for better discoverability in your IDE:&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;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;EventNames&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;LOAD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;PLAY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;PAUSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;STOP&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;AppEvents&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;EventNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LOAD&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;filename&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;format&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;totalTimeMs&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="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;EventNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;currentTimeMs&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="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;EventNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PLAY&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;EventNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PAUSE&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;EventNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STOP&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, you can call your triggers and subscriptions 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="nx"&gt;eventSystem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;EventNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PLAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;eventSystem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;EventNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;currentTime&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;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="s2"&gt;`Current time: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;currentTime&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="o"&gt;-------------&lt;/span&gt;

&lt;span class="nx"&gt;This&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="nx"&gt;was&lt;/span&gt; &lt;span class="nx"&gt;published&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;my&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;personal&lt;/span&gt; &lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//parastudios.de/), first. &lt;/span&gt;

&lt;span class="nx"&gt;You&lt;/span&gt; &lt;span class="nx"&gt;should&lt;/span&gt; &lt;span class="nx"&gt;follow&lt;/span&gt; &lt;span class="nx"&gt;me&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;more&lt;/span&gt; &lt;span class="nx"&gt;tips&lt;/span&gt; &lt;span class="nx"&gt;like&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Click&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;follow&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;sidebar&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="err"&gt;🚀&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
