<?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: Denis Morozov</title>
    <description>The latest articles on DEV Community by Denis Morozov (@frozer).</description>
    <link>https://dev.to/frozer</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%2F224168%2F14246663-e58d-4d61-a308-8513cf9cb50b.png</url>
      <title>DEV Community: Denis Morozov</title>
      <link>https://dev.to/frozer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/frozer"/>
    <language>en</language>
    <item>
      <title>Nuxt 3 Deployment: Automating Environment-Specific Builds with GitHub Actions</title>
      <dc:creator>Denis Morozov</dc:creator>
      <pubDate>Sun, 23 Feb 2025 22:39:28 +0000</pubDate>
      <link>https://dev.to/frozer/nuxt-3-deployment-automating-environment-specific-builds-with-github-actions-31h4</link>
      <guid>https://dev.to/frozer/nuxt-3-deployment-automating-environment-specific-builds-with-github-actions-31h4</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hi All,  &lt;/p&gt;

&lt;p&gt;Recently, I needed to prepare a deployment procedure for a Nuxt-based frontend. Based on my experience, I initially expected to create a single build that could be used across all environments — QA, UAT, and PROD. However, I discovered that I had to generate separate builds for each environment, each with its own &lt;code&gt;.env&lt;/code&gt; file defining the Strapi URL and other parameters. This limitation arose due to the &lt;code&gt;@nuxt/image&lt;/code&gt; library which does not support runtime configuration option for its plugins (we're using &lt;a href="https://strapi.io" rel="noopener noreferrer"&gt;Strapi&lt;/a&gt; to store images).  &lt;/p&gt;

&lt;p&gt;Different teams have different workflows—some prefer a single build deployed across all environments (&lt;em&gt;Build-Once, Deploy Everywhere&lt;/em&gt;), while others go with separate builds per environment. Both approaches have their &lt;em&gt;pros&lt;/em&gt; and &lt;em&gt;cons&lt;/em&gt;.  &lt;/p&gt;

&lt;p&gt;I prefer the first approach due to the following advantages:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Program behavior remains unchanged, ensuring consistency. If only configuration files or databases change, you can guarantee that something working in preproduction will work in production simply by ensuring those configurations match.
&lt;/li&gt;
&lt;li&gt;It's slightly more robust. You maintain only one "gold" build, reducing the risk of mistakenly deploying a dev version to QA, and so on.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, due to the limitation I mentioned above, I couldn't use this approach and had to venture into the unfamiliar waters of per-environment builds. Unfortunately, Nuxt’s documentation on this topic is unclear (see &lt;a href="https://nuxt.com/docs/getting-started/deployment" rel="noopener noreferrer"&gt;Nuxt Deployment Guide&lt;/a&gt;), focusing more on exact deployment or &lt;a href="https://nuxt.com/docs/getting-started/testing" rel="noopener noreferrer"&gt;testing procedures&lt;/a&gt; rather than workflow guidance for development teams.  &lt;/p&gt;

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

&lt;p&gt;To address this issue, I explored and documented a structured approach to handling environment-specific builds in Nuxt, aiming to bridge this gap in the official documentation.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Technology Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nuxt 3&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strapi&lt;/strong&gt; as the backend
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js 18&lt;/strong&gt; for building and running
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt; for storing source code and artifacts
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Two environments: &lt;strong&gt;UAT&lt;/strong&gt; and &lt;strong&gt;PROD&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;A changelog for each release
&lt;/li&gt;
&lt;li&gt;The ability to initiate a release by assigning a tag to a commit
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;@nuxt/image&lt;/code&gt; plugin requires a statically injected (&lt;code&gt;.env&lt;/code&gt;) &lt;code&gt;STRAPI_URL&lt;/code&gt;, which cannot be placed in &lt;code&gt;runtimeConfig&lt;/code&gt; (See the &lt;a href="https://nuxt.com/docs/getting-started/configuration#runtimeconfig-vs-appconfig" rel="noopener noreferrer"&gt;Nuxt Runtime Config Docs&lt;/a&gt;)
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Workflow
&lt;/h2&gt;

&lt;p&gt;This workflow automates the build and release process by generating environment-specific builds and creating a changelog for each release.  &lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Triggering the Workflow&lt;/strong&gt;: The build and release process is triggered when a tag matching &lt;code&gt;v*&lt;/code&gt; is pushed. To deploy to UAT, for example, we assign a tag to the respective commit. The first tag (e.g., &lt;code&gt;v1.0.0&lt;/code&gt;) requires a manual &lt;code&gt;CHANGELOG.md&lt;/code&gt; generation via:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   git log %YOUR_FIRST_COMMIT%..&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ env.TAG_NAME &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;--pretty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;format:&lt;span class="s2"&gt;"%h %s by %an"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please refer to the Git documentation for a &lt;a href="https://git-scm.com/docs/pretty-formats" rel="noopener noreferrer"&gt;detailed explanation of the output format&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Repository Permissions&lt;/strong&gt;: The workflow requires &lt;code&gt;write&lt;/code&gt; access to create releases and attach artifacts.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Changelog Generation&lt;/strong&gt;: The difference between the current and previous tags is used to generate a meaningful changelog.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment-Specific Configuration&lt;/strong&gt;: GitHub Action Secrets store the Strapi backend URL for each environment, ensuring secure and correct configurations.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Release Artifacts&lt;/strong&gt;: Each release includes two build artifacts, one for UAT and one for PROD, ensuring proper separation.
&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;GitHub Actions Workflow:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v*'&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;artifact_package_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;project61-nuxt&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;18.x&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;UAT&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;PROD&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&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;Save tag as environment variable&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 "TAG_NAME=${GITHUB_REF#refs/tags/}" &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;Store artifact package name as environment variable&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 ARTIFACT_NAME=${{ env.artifact_package_name }}-${{ env.TAG_NAME }}-${{ matrix.environment }}.tar.gz &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;Use Node.js ${{ matrix.node-version }}&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.node-version }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;yarn install --immutable --check-cache --non-interactive&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;Load Environment Variables&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;echo "STRAPI_URL=${{ secrets[format('{0}_STRAPI_URL', matrix.environment)] }}" &amp;gt; .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;Build Nuxt App&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;npx nuxt build&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;Pack build artifacts&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;&amp;gt;&lt;/span&gt;
          &lt;span class="s"&gt;tar zcvf ${{ env.ARTIFACT_NAME }} .output&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;Upload Artifact&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&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;${{ env.ARTIFACT_NAME }}&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.ARTIFACT_NAME }}&lt;/span&gt;

  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&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;Download All Artifacts&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dist&lt;/span&gt;
          &lt;span class="na"&gt;merge-multiple&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;Save tag as environment variable&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 "TAG_NAME=${GITHUB_REF#refs/tags/}" &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;Store previous tag&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 "PREV_TAG_NAME=$(git describe --abbrev=0 --tags $(git rev-list --tags --skip=1 --max-count=1))" &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;Create changelog&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;echo ${{ env.PREV_TAG_NAME }}..${{ env.TAG_NAME }}   &lt;/span&gt;
          &lt;span class="s"&gt;git log ${{ env.PREV_TAG_NAME }}..${{ env.TAG_NAME }} --pretty=format:"%h %s by %an" &amp;gt; CHANGELOG.md&lt;/span&gt;
          &lt;span class="s"&gt;ls -l dist/&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;Create Release&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;gh release create ${{ github.ref_name }} dist/*.tar.gz --title "Release ${{ github.ref_name }}" --notes-file CHANGELOG.md&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This workflow streamlines the deployment process by automating build creation, versioning, and changelog generation. Each environment gets its own dedicated build, ensuring proper separation and accurate testing. By using GitHub Action Secrets, environment-specific configurations remain secure, and every release includes artifacts for both UAT and PROD.  &lt;/p&gt;

&lt;p&gt;Oh, and one final discovery—while generating changelogs, we realized that &lt;em&gt;some developers&lt;/em&gt; might need a refresher on writing meaningful commit messages. Turns out, "fix stuff" and "update" aren't the most helpful descriptions when trying to track changes! 😅&lt;/p&gt;

</description>
      <category>nuxt</category>
      <category>webdev</category>
      <category>cicd</category>
      <category>github</category>
    </item>
    <item>
      <title>Async Cache Service - you don't need "isLoading" anymore!</title>
      <dc:creator>Denis Morozov</dc:creator>
      <pubDate>Tue, 23 May 2023 02:07:27 +0000</pubDate>
      <link>https://dev.to/frozer/async-cache-service-1lj7</link>
      <guid>https://dev.to/frozer/async-cache-service-1lj7</guid>
      <description>&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;Simple Javascript library to manage asynchronous cache/storage in app/service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Category Submission:
&lt;/h3&gt;

&lt;p&gt;The best category matches my project is &lt;strong&gt;Wacky Wildcards&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  App Link
&lt;/h3&gt;

&lt;p&gt;You can find library page and package here - &lt;a href="https://www.npmjs.com/package/async-cache-service"&gt;Async Cache Service on NPM&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

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

&lt;h3&gt;
  
  
  Description
&lt;/h3&gt;

&lt;p&gt;The asynchronous cache service is designed to handle the storage and retrieval of data in a non-blocking manner. When data is being loaded into the cache, the service returns a Promise object to the requester. This Promise serves as a placeholder or notification that the data is currently in the process of being fetched.&lt;/p&gt;

&lt;p&gt;Once the data loading process is complete and the data is successfully retrieved, the cache service resolves all previously issued Promises associated with that specific data. By resolving the Promises, the cache service effectively provides the updated and complete data to all subscribers or requesters who were waiting for it.&lt;/p&gt;

&lt;h4&gt;
  
  
  How to use
&lt;/h4&gt;

&lt;p&gt;Typescript Example:&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;AsyncCacheService&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;async-cache-service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// by default - the cache is never expire&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cacheService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AsyncCacheService&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// with 5min expiration&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cacheService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AsyncCacheService&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// as a dependency with 15min expiration&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;SomeDataService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;cacheService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AsyncCacheService&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="nx"&gt;_000&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;Javascript Example:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;AsyncCacheService&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;async-cache-service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;DataService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// cache expires in 15min&lt;/span&gt;
  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cacheService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AsyncCacheService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&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;cacheService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cacheService&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="nx"&gt;getData&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;if&lt;/span&gt; &lt;span class="p"&gt;(&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;cacheService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isExpired&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cacheService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refreshItem&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// retrieve data from somewhere&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/some/resource&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&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;cacheService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setItem&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="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="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cacheService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flushItem&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="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&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;cacheService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getItem&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the &lt;em&gt;getData&lt;/em&gt; method of DataService is called, it returns a Promise, and DataService initiates the data load. During this process, all incoming calls to &lt;em&gt;getData&lt;/em&gt; will receive Promises that resolve after the data load. In other words, we eliminate the necessity to have a well-known &lt;em&gt;isLoading&lt;/em&gt; flag in our DataService and provide an elegant way to handle multiple data requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Source Code
&lt;/h3&gt;

&lt;p&gt;You can find library sources here - &lt;a href="https://github.com/frozer/async-cache-service"&gt;Async Cache Service on Github&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Permissive License
&lt;/h3&gt;

&lt;p&gt;This project is under &lt;a href="https://github.com/frozer/async-cache-service/blob/main/LICENSE"&gt;MIT License&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Background (What made you decide to build this particular app? What inspired you?)
&lt;/h2&gt;

&lt;p&gt;In the preceding article &lt;a href="https://dev.to/frozer/vanilla-js-data-cache-service-1ei2"&gt;Vanilla JS data cache service&lt;/a&gt;, I emphasized the issue of asynchronous data cache and outlined a potential solution. However, after thoughtful consideration, I opted to integrate the suggested approach into a compact Javascript library, accompanied by comprehensive unit tests and leveraging Github Actions for complete CI/CD workflow - encompassing build -&amp;gt; test -&amp;gt; package -&amp;gt; deploy. The resultant library, named "async-data-service", introduces an efficient method to manage asynchronous cache/storage operations with minimal developer overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I built it (How did you utilize GitHub Actions or GitHub Codespaces? Did you learn something new along the way? Pick up a new skill?)
&lt;/h3&gt;

&lt;p&gt;Well, I used TypeScript (love it!) and Rollup for bundling. For my projects, I prefer to use the &lt;a href="https://nx.dev"&gt;Nx build system&lt;/a&gt;it provides me with a lot of pre-defined tools and allows me to focus on what I do. For unit tests, I used the Jest framework. The real challenge for me in this project was to utilize GitHub Actions for NPM publishing. &lt;/p&gt;

&lt;p&gt;The most available resources describe the publishing process in a pretty straightforward way, like (taken from official :&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish Package to npmjs&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class="c1"&gt;# Setup .npmrc file to publish to npm&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;16.x'&lt;/span&gt;
          &lt;span class="na"&gt;registry-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://registry.npmjs.org'&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;npm publish&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;NODE_AUTH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.NPM_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which isn't really helpful. How does "npm ci" relate to "npm publish"? What is the difference between a "published" release and a "created" release? And finally, where are my built artifacts?&lt;/p&gt;

&lt;p&gt;So, I need to find answers. The first thing I resolved was the question regarding the difference between "created" and "published" events for releases. Actually, the "created" event is emitted only for draft releases, while the "published" event is emitted for non-draft releases. Therefore, I will use the "published" event in my Github Actions.&lt;/p&gt;

&lt;p&gt;Next, I have to consider that Nx puts build artifacts into the "dist/packages/async-cache-service" folder. Therefore, I need to run "npm publish" from there. As a result of my GitHub Actions efforts, I implemented two actions - "push" and "publish".&lt;/p&gt;

&lt;p&gt;The "push" action is used to handle the push event of a pull request and performs the build and test of the proposed changes:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Node.js CI&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;18.x&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&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;Use Node.js ${{ matrix.node-version }}&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.node-version }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;npm run build --if-present&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;npm test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The "publish" action is used to handle the release publish event and performs the build, test, and publish steps for the release in two steps:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish Package to npmjs&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;18.x&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&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;Use Node.js ${{ matrix.node-version }}&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.node-version }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;npm ci&lt;/span&gt;
          &lt;span class="s"&gt;npm run build --if-present&lt;/span&gt;
          &lt;span class="s"&gt;npm test &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;Archive production artifacts&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&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;artifact&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dist/packages/async-cache-service&lt;/span&gt;

  &lt;span class="na"&gt;download&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download build step artifact&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&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;artifact&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;18.x'&lt;/span&gt;
          &lt;span class="na"&gt;registry-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://registry.npmjs.org'&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;npm publish&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;NODE_AUTH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.NPM_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, this action contains two steps - &lt;strong&gt;build&lt;/strong&gt; and &lt;strong&gt;publish&lt;/strong&gt;. The &lt;strong&gt;build&lt;/strong&gt; step performs the build, test, and stores the build artifacts into the Github storage. The &lt;strong&gt;publish&lt;/strong&gt; step retrieves the just created build from the build node and publishes it into the NPM registry.&lt;/p&gt;

&lt;p&gt;In summary, I find myself very interested in using GitHub Actions for my current and future projects. I love the way it works. My next step is to adopt GitHub Actions for another project I'm working on, written in C.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Resources/Info
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/59319281/github-action-different-between-release-created-and-published"&gt;Github Action different between release created and published&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts"&gt;Storing workflow data as artifacts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages"&gt;Publishing Node.js packages&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>githubhack23</category>
      <category>nx</category>
      <category>typescript</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Demystifying Nx's "dependsOn" configuration</title>
      <dc:creator>Denis Morozov</dc:creator>
      <pubDate>Sun, 07 May 2023 02:07:18 +0000</pubDate>
      <link>https://dev.to/frozer/demystifying-nxs-dependson-configuration-f4b</link>
      <guid>https://dev.to/frozer/demystifying-nxs-dependson-configuration-f4b</guid>
      <description>&lt;p&gt;We're using Nx in our multirepos to build our Node/Nest apps, as well as Angular. It was a long story how we migrated from Angular Workspace repo for web apps, and single repos for NodeJS/Nest, but in this article I'd like to focus on "dependsOn" property of &lt;code&gt;project.json&lt;/code&gt; and how we treat it.&lt;/p&gt;

&lt;p&gt;Nx documentation contains a good portion of "dependsOn" description - &lt;a href="https://nx.dev/reference/project-configuration#dependson"&gt;https://nx.dev/reference/project-configuration#dependson&lt;/a&gt; but as for me it still not clear how does "dependsOn" inside "targetDefaults" interact with project-level "dependsOn".&lt;/p&gt;

&lt;p&gt;In our project we're utilizing some shared libraries which can be used for Angular apps and for NodeJS/Nest apps as well, i.e. some models definitions and so on. Thus, it is important that those libraries should be built before other apps. That's why we included libraries definitions into "targetDefaults". From other hand we have got some script which need to be run before build, so we create a separate task inside &lt;code&gt;project.json&lt;/code&gt; to describe it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; "injectBuildVersion": {
   "executor": "nx/run-script",
   "options": {
     "script": "inject-build-version some-app"
   }
 }
 "targetDefaults": {
   "build": {
     "dependsOn": "some-lib"
   }
 },
 "dependsOn": ["injectBuildVersion"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;as a result the application was failing to build, because library wasn't built yet. But we noticed that "injectBuildVersion" was executing correctly.&lt;/p&gt;

&lt;p&gt;The reason of such behavior is that project-level "dependsOn" overrides "targetDefaults". Moreover, this behavior already described somewhere in Nx issues :-) To make it work, we have to include a special dependency "^build" into project-level "dependsOn" property, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; "injectBuildVersion": {
   "executor": "nx/run-script",
   "options": {
     "script": "inject-build-version some-app"
   }
 }
 "targetDefaults": {
   "build": {
     "dependsOn": "some-lib"
   }
 },
 "dependsOn": ["injectBuildVersion", "^build"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, everything works as expected, build version value (which comes from Jenkins) injects into &lt;code&gt;environment.ts&lt;/code&gt; file, and then Nx performs the build with all necessary dependencies. Good job, Nx!  &lt;/p&gt;

</description>
      <category>nx</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Singletons in JS And How To Use Them</title>
      <dc:creator>Denis Morozov</dc:creator>
      <pubDate>Wed, 07 Sep 2022 02:20:18 +0000</pubDate>
      <link>https://dev.to/frozer/singletons-in-js-and-how-to-use-them-2abb</link>
      <guid>https://dev.to/frozer/singletons-in-js-and-how-to-use-them-2abb</guid>
      <description>&lt;p&gt;Hi Team,&lt;/p&gt;

&lt;p&gt;Today, I'd like to tell about singletons and how we are using them on daily-basis. &lt;br&gt;
So, singleton pattern allows us to create single instance of particular class. For which purposes  can it be helpful? We are using them for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;data interchange services, which take care about data transferring to/from underlying services&lt;/li&gt;
&lt;li&gt;data processing services, which can be instantiated only once on application start&lt;/li&gt;
&lt;li&gt;cache services&lt;/li&gt;
&lt;li&gt;testing purposes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, we decided to continue with previously described Cache Service (&lt;a href="https://dev.to/frozer/vanilla-js-data-cache-service-1ei2"&gt;https://dev.to/frozer/vanilla-js-data-cache-service-1ei2&lt;/a&gt;), for some reasons we don't need multiple &lt;br&gt;
cache services in our app, so how we are going to create a singletone?&lt;/p&gt;

&lt;p&gt;Let's start with very basic code to create a singletone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class SingletoneService {
  static instance;

  getInstance(args) {
    if (!SingletoneService.instance) {
      SingletoneService.instance = new SingletoneService(...args);
    }

    return SingletoneService.instance;
  }

  constructor(args) {
    // do something with args
  }

  doSomething() {
    // do something
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, how does it work? In the main program we can create instance of singleton by calling this &lt;em&gt;getInstance&lt;/em&gt; method with arguments (if we need them):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function main() {
  const instance = SingletoneService.getInstance();

  instance.doSomething();
}
main();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By calling the &lt;em&gt;getInstance&lt;/em&gt; method it checks for static &lt;strong&gt;instance&lt;/strong&gt; field value of class SingletoneService, and then instantiate a new object based on that class using the &lt;em&gt;new&lt;/em&gt; keyword. If it exists, it simply returns the existing instance.&lt;/p&gt;

&lt;p&gt;Now, once the draft implementation is done, try to move this functionality into our CacheService:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class SomeServiceWithDataCache {
  static instance;

  getInstance(args) {
    if (!SomeServiceWithDataCache.instance) {
      SomeServiceWithDataCache.instance = new SomeServiceWithDataCache(...args);
    }

    return SomeServiceWithDataCache.instance;
  }

  constructor() {
    this.cache = {
      isLoading: false,
      expire: 0,
      data: null
    };
    this.cacheSubscriptions = [];
  }
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, you can use singletones to inject dependency to your class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class DataProcessingService {
  // use existing DataCacheService instance by default
  constructor(cacheService = DataCacheService.getInstance()) {
    // do something in constructor)
  }
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which allows you to implement code isolation and Open-Closed principle.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>beginners</category>
      <category>oop</category>
    </item>
    <item>
      <title>Vanilla JS data cache service</title>
      <dc:creator>Denis Morozov</dc:creator>
      <pubDate>Mon, 11 Jul 2022 01:35:09 +0000</pubDate>
      <link>https://dev.to/frozer/vanilla-js-data-cache-service-1ei2</link>
      <guid>https://dev.to/frozer/vanilla-js-data-cache-service-1ei2</guid>
      <description>&lt;p&gt;Hi Team,&lt;/p&gt;

&lt;p&gt;Some time ago I faced an interesting task to solve, and today I'd like to share what I found. Let's imagine that we have a service which needs to load some data (sounds familiar, huh?), &lt;/p&gt;

&lt;p&gt;Also we've got several "customers" which utilize the data retrieval method from this service, and for first look all works fine, but when I opened the Developers Console, I was wondering how much duplicate data requests we got there! &lt;/p&gt;

&lt;p&gt;Here is our initial implementation of that service (I rewrote it to be more general):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// let's assume we have some network slowness there...
const delayedDataFetch = () =&amp;gt; new Promise((resolve) =&amp;gt;
  setTimeout(() =&amp;gt; resolve([1, 2, 3]), 2000)
);

class SomeService {
  async getData() {
    return await delayedDataFetch();
  }
}

// ...
// emulation of parallel requests for the same data
function main() {
  const service = new SomeService();
  await Promise.all([
    service.getData(1),
    service.getData(2)
  ]).then(
    ([res1, res2]) =&amp;gt; {
      // receives correct data
      console.log(`1. ${JSON.stringify(res1)}`);
      // receives correct data once again, 
      // from the second call to delayedDataFetch
      // and I want to get rid of that second data call to server
      console.log(`2. ${JSON.stringify(res2)}`);
    }
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well, my first thought was to implement a some little cache for such data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const CACHE_EXPIRATION_TIME_MS = 5000;

class SomeService {
  constructor() {
    this.cache = {
      isLoading: false,
      data: null,
      // current timestamp plus expiration delay
      expire: 0
    };
  }

  // added sequenceId for more clarity
  async getData(sequenceId) {
    console.log(`Received ${sequenceId} request`);

    // if cache is expired and isLoading is false 
    // - initiate data update from server
    if (this.cache.expire &amp;lt; new Date().getTime() &amp;amp;&amp;amp; !this.cache.isLoading) {
      this.cache.isLoading = true;

      this.cache = {
        isLoading: false,
        data: await delayedDataFetch(),
        expire: new Date().getTime() + CACHE_EXPIRATION_TIME_MS
      };
    }

    console.log(`Response ${sequenceId} with data`);
    return this.cache.data;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works! At least, as I can see from Network Tab the number of networks requests reduced to one. Looks like I can submit this code to repo. &lt;/p&gt;

&lt;p&gt;But... hold on, I just started to receive errors from other parts of applications, and the main issue here, that they are different from time to time. Quick searching gives me a new problem - the new code returns null data from time to time. &lt;/p&gt;

&lt;p&gt;So, this simple approach didn't solve the problem completely. Let's look closely to this code of &lt;code&gt;getData&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  // a first request switch isLoading to true, and...
  if (this.cache.expired &amp;lt; new Date().getTime() &amp;amp;&amp;amp; !this.cache.isLoading) {
    this.cache.isLoading = true;
    // ... do something
  }

  // the second one simply receives empty data... 
  console.log(`Response ${sequenceId} with data`);
  return this.cache.data;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gotcha! I found! But how can we say our "customers" that they need to wait? Of course, I can switch to RxJS f.e., but I don't want to bubble up the size or our bundle, and I was eager to reach the goal using more vanilla approach.&lt;/p&gt;

&lt;p&gt;I started about to return an another Promise to the second response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  // a first request switch isLoading to true, and...
  if (this.cache.expire &amp;lt; new Date().getTime() &amp;amp;&amp;amp; !this.cache.isLoading) {
    this.cache.isLoading = true;
    // ... do something
  }

  if (this.cache.isLoading) {
    // it should return something to client in such cases, 
    // which "something" should be resolved back to refreshed data
    console.log(`Response ${sequenceId} with unresolved promise`);
    return new Promise(resolve =&amp;gt; ???);
  }

  // the second one simply receives empty data... 
  console.log(`Response ${sequenceId} with data`);
  return this.cache.data;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I was thinking about how I can resolve it, and a flash brighten my brain :-) - I need a simple PUB/SUB!&lt;br&gt;
I decided to add an additional subscribers pool into &lt;code&gt;SomeService&lt;/code&gt; class and renamed it to &lt;code&gt;SomeServiceWithDataCache&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;class SomeServiceWithDataCache {
  constructor() {
    this.cache = {
      isLoading: false,
      expire: 0,
      data: null
    };
    this.cacheSubscriptions = [];
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and change the code of &lt;code&gt;getData&lt;/code&gt; method accordingly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  async getData(sequenceId) {
    console.log(`Received ${sequenceId} request`);
    if (this.cache.expire &amp;lt; new Date().getTime() &amp;amp;&amp;amp; !this.cache.isLoading) {
      this.cache.isLoading = true;

      this.cache = {
        isLoading: false,
        data: await delayedDataFetch(),
        expire: new Date().getTime() + CACHE_EXPIRATION_TIME_MS
      };

      // once we receive data - iterate over subscribers pool, 
      // and resolve each with received data, and finally drop
      // subscriptions
      await Promise.all(
        this.cacheSubscriptions.map((res) =&amp;gt; res(this.cache.data))
      ).then(() =&amp;gt; (this.cacheSubscriptions = []));
    }

    if (this.cache.isLoading) {
      console.log(`Response ${sequenceId} with unresolved promise`);
      // push the promise resolve into subscribers pool
      return new Promise((resolve) =&amp;gt; this.cacheSubscriptions.push(resolve));
    }

    console.log(`Response ${sequenceId} with data`);
    return this.cache.data;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well, how it works - it receives the first data request from client, founds that internal data cache is expired, and initiates data loading. For the second request it returns the unresolved Promise, and put the resolver function into the subscribers pool. Once it receives data from server it iterates through the subscribers pool and call resolver with an actual data. That's it.&lt;/p&gt;

&lt;p&gt;You can find complete code for the solution on CodeSandBox - &lt;a href="https://codesandbox.io/s/simple-data-caching-service-cds83d"&gt;https://codesandbox.io/s/simple-data-caching-service-cds83d&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>SCRUM Retrospective meeting is for what?</title>
      <dc:creator>Denis Morozov</dc:creator>
      <pubDate>Sat, 19 Sep 2020 18:19:03 +0000</pubDate>
      <link>https://dev.to/frozer/scrum-retrospective-meeting-for-what-f7d</link>
      <guid>https://dev.to/frozer/scrum-retrospective-meeting-for-what-f7d</guid>
      <description>&lt;p&gt;Hi Devs,&lt;/p&gt;

&lt;p&gt;Most of us tackled with SCRUM at least at once. As for me, I do my job about five years using the SCRUM methology. It helpful, it allows us to deliver features and fixes more predictive and transparent, and sometimes, in time :-) you know what I mean. I was a SCRUM master, team leader, ordinal developer, chief... and know what? I still can't understand why we need retrospective. &lt;/p&gt;

&lt;p&gt;Last Friday we had one, and our SCRUM master opened a Word document with empty three-column table and asked us the question - "John/Sam/Dan, what you think about last sprint, what was good, what we need to change and what is ugly?". You know that question and I always in trouble what I should to answer. Look, I have a lot of JIRA's, I communicated well with others (BAs, QAs) to get them to solve, and I solved them. Nothing to say - ALL GOOD! And everyone of us said nothing, because - all works well. Right after the meeting, our SCRUM master said that my team-mates "aren't evolved into product". Hey dude, these guys deliver features/fixes every two week in-time and mostly error-free, you can't judge them for the silence on the Retrospective meeting!&lt;/p&gt;

&lt;p&gt;If I can't communicate with someone, have hardware problem, or my IDE license has been expired - I don't need to wait until end of the sprint, to tell about it on the retrospective, otherwise I won't do my job well.&lt;/p&gt;

&lt;p&gt;As SCRUM methology said - "discuss problems and circumstances with other team-mates..." Cool, but I have time to discuss about on the daily meetings.&lt;/p&gt;

&lt;p&gt;So, what do you think about Retrospective? How do we need to cook it well?&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
