<?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: Xkonti</title>
    <description>The latest articles on DEV Community by Xkonti (@xkonti).</description>
    <link>https://dev.to/xkonti</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%2F251904%2F5dd70d59-54e5-4314-a0d3-e2d35f56fa00.jpeg</url>
      <title>DEV Community: Xkonti</title>
      <link>https://dev.to/xkonti</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/xkonti"/>
    <language>en</language>
    <item>
      <title>Adding Mermaid diagrams to Astro MDX</title>
      <dc:creator>Xkonti</dc:creator>
      <pubDate>Fri, 05 Jul 2024 21:17:34 +0000</pubDate>
      <link>https://dev.to/xkonti/adding-mermaid-diagrams-to-astro-mdx-2l55</link>
      <guid>https://dev.to/xkonti/adding-mermaid-diagrams-to-astro-mdx-2l55</guid>
      <description>&lt;p&gt;Adding &lt;a href="https://mermaid.js.org/" rel="noopener noreferrer"&gt;Mermaid diagrams&lt;/a&gt; to Astro pages has a surprising number of challenges. Most of the solutions out there rely on using headless browsers to render the diagrams. This approach has a few drawbacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need to install a headless browser on your machine to be able to build your site&lt;/li&gt;
&lt;li&gt;It might prevent your site from building on CI/CD (like Cloudflare Pages)&lt;/li&gt;
&lt;li&gt;It might slow down your site's build time significantly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reason for the popularity of this approach is that Mermaid.js relies on browser APIs to lay the diagrams out. Astro doesn't have access to these APIs, so it can't render the diagrams directly.&lt;/p&gt;

&lt;p&gt;Fortunately, there's another option: rendering the diagrams on the client side. Definitely not ideal, as suddenly our pages won't be fully pre-rendered, but in case only some of your pages have diagrams, it's still a viable option. Especially that it doesn't require any additional setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  High level overview
&lt;/h2&gt;

&lt;p&gt;The idea is to use the &lt;a href="https://www.npmjs.com/package/mermaid" rel="noopener noreferrer"&gt;official mermaid package&lt;/a&gt; directly and let it render the diagrams on the client side. In my case I want to have a diagram as a separate component to add some additional functionality, like the ability to display the diagram's source code. This decision has one side effect: &lt;strong&gt;The component won't work in pure Markdown files. It will only work in MDX files.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To make it work in pure Markdown files, one would need to create a rehype/remark plugin, but I didn't feel like it was worth the effort - I use MDX files for everything as it provides more functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the component
&lt;/h2&gt;

&lt;p&gt;First we need to install the mermaid package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# npm&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;mermaid

&lt;span class="c"&gt;# pnpm&lt;/span&gt;
pnpm add mermaid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's create the component. It will be an Astro component as we don't need any additional framework functionality for this. Let's call it &lt;code&gt;Mermaid.astro&lt;/code&gt; - I placed in in &lt;code&gt;stc/components/markdown&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
export interface Props {
  title?: string;
}
const { title = "" } = Astro.props;
---
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;mermaid&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;mermaid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;figure&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;figcaption&amp;gt;&lt;/span&gt;{title}&lt;span class="nt"&gt;&amp;lt;/figcaption&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;pre&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mermaid not-prose"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;slot&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/figure&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing special here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We make the component accept a &lt;code&gt;title&lt;/code&gt; prop so that we can display a nice title - relying on mermaid's built-in titles itn't optimal as the title will show up in various sizes depending on the diagram's size.&lt;/li&gt;
&lt;li&gt;We add a script that will import the mermaid package on the client side. It's worth noting that Astro will include that script only once on the page no matter how many times we use the component. Simply including the &lt;code&gt;mermaid&lt;/code&gt; will register a &lt;code&gt;DOMContentLoaded&lt;/code&gt; event listener for the mermaid renderer.&lt;/li&gt;
&lt;li&gt;The mermaid renderer looks through the entire page for &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; elements with the &lt;code&gt;mermaid&lt;/code&gt; class. Including it here will ensure that the diagram code will be processed by mermaid. In my case I also need to add the &lt;code&gt;not-prose&lt;/code&gt; class to remove some conflicts with my markdown styling.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;&amp;lt;slot /&amp;gt;&lt;/code&gt; element will be replaced with the mermaid code wrapped by this component.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now let's try to use it in an MDX file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Testing mermaid in Astro&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

import Mermaid from "@components/markdown/Mermaid.astro";

&lt;span class="nt"&gt;&amp;lt;Mermaid&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Does it work?"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
flowchart LR
    Start --&amp;gt; Stop
&lt;span class="nt"&gt;&amp;lt;/Mermaid&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the results is:&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%2F6lv7cmzkrv4ff3s7f912.png" 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%2F6lv7cmzkrv4ff3s7f912.png" alt="Screenshot showing mermaid complaining about a syntax error" width="800" height="109"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is where inspecting the page source comes in handy. This way we can see what Astro rendered before mermaid tried to process it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;figure&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;figcaption&amp;gt;&lt;/span&gt;Does it work?&lt;span class="nt"&gt;&amp;lt;/figcaption&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;pre&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mermaid not-prose"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;flowchart LR
Start —&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt; Stop&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/figure&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are several issues here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Our code is wrapped in &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; tag, confusing the hell out of mermaid&lt;/li&gt;
&lt;li&gt;The double dash &lt;code&gt;--&lt;/code&gt; has been replaced with an em dash &lt;code&gt;—&lt;/code&gt; which is not what mermaid expects&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;&amp;gt;&lt;/code&gt; character has been replaced with &lt;code&gt;&amp;amp;gt;&lt;/code&gt; which messes thing up even more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What could have caused this? Markdown.&lt;/p&gt;

&lt;p&gt;When the MDX page is rendered, all that is not explicitly an MDX element, is processed by markdown. This includes everything wrapped in the &lt;code&gt;&amp;lt;Mermaid&amp;gt;&lt;/code&gt; component. Markdown saw some text - it marked it as a paragraph, escaped the scary characters (&lt;code&gt;&amp;gt;&lt;/code&gt;), and then &lt;em&gt;prettyfied&lt;/em&gt; it by consolidating the dashes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solving the issue
&lt;/h2&gt;

&lt;p&gt;There are several ways to solve this issue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pass the code as a string to the component - deal with manually adding &lt;code&gt;\n&lt;/code&gt; to simulate new lines as HTML doesn't support multiline arguments.&lt;/li&gt;
&lt;li&gt;Load the diagrams as separate files using the &lt;code&gt;import&lt;/code&gt; statement - don't have everything in one place.&lt;/li&gt;
&lt;li&gt;Go the crazy route and pass a code block to the component 🤪&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course I went for the last one. It might sound like a great idea, but depending on the way&lt;br&gt;
your setup renders the code blocks, it might be a bit of a pain to deal with. Let's try it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Testing mermaid in Astro&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

import Mermaid from "@components/markdown/Mermaid.astro";

&lt;span class="nt"&gt;&amp;lt;Mermaid&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Does it work?"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="sb"&gt;`` `mermaid
flowchart LR
    Start --&amp;gt; Stop
``&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Mermaid&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ Please remove the extra space from triple backticks - dev.to has really bad code block support that doesn't allow nesting. Consider reading this article on &lt;a href="https://xkonti.tech/blog/astro-mermaid-mdx" rel="noopener noreferrer"&gt;my website&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://xkonti.tech/blog/astro-mermaid-mdx" rel="noopener noreferrer"&gt;My blog&lt;/a&gt; uses &lt;a href="https://expressive-code.com/" rel="noopener noreferrer"&gt;Expressive Code&lt;/a&gt;] to render the code blocks, and therefore the page's source code will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;figure&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;figcaption&amp;gt;&lt;/span&gt;Does it work?&lt;span class="nt"&gt;&amp;lt;/figcaption&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;pre&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mermaid not-prose"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"expressive-code"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;figure&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"frame"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;figcaption&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"header"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/figcaption&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;pre&lt;/span&gt; &lt;span class="na"&gt;data-language=&lt;/span&gt;&lt;span class="s"&gt;"mermaid"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;code&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ec-line"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"code"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"--0:#B392F0;--1:#24292E"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;flowchart LR&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ec-line"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"code"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"indent"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"--0:#B392F0;--1:#24292E"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;    &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"--0:#B392F0;--1:#24292E"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Start --&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt; Stop&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/code&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"copy"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
            &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Copy to clipboard"&lt;/span&gt;
            &lt;span class="na"&gt;data-copied=&lt;/span&gt;&lt;span class="s"&gt;"Copied!"&lt;/span&gt;
            &lt;span class="na"&gt;data-code=&lt;/span&gt;&lt;span class="s"&gt;"flowchart LR    Start --&amp;gt; Stop"&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/figure&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/figure&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wow. This added a bit more markup to the page... but what's that? A &lt;code&gt;copy&lt;/code&gt; button? How does that work? Take a look at it's markup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
  &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Copy to clipboard"&lt;/span&gt;
  &lt;span class="na"&gt;data-copied=&lt;/span&gt;&lt;span class="s"&gt;"Copied!"&lt;/span&gt;
  &lt;span class="na"&gt;data-code=&lt;/span&gt;&lt;span class="s"&gt;"flowchart LR    Start --&amp;gt; Stop"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole source code of our diagram in a pleasant HTML argument string. It's easy to extract it and give it to mermaid on the client side. Let's modify our &lt;code&gt;Mermaid.astro&lt;/code&gt; component to do exactly that!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;No copy button?&lt;/strong&gt;&lt;br&gt;
If you're not using Expressive Code and your code blocks don't have the handy &lt;code&gt;copy&lt;/code&gt; button, I included an alternative code snipped at the end of the article.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Preparing the component
&lt;/h2&gt;

&lt;p&gt;First, let's rework the component's HTML markup. We'll wrap it in a &lt;code&gt;figure&lt;/code&gt; element and place the code block indside a &lt;code&gt;details&lt;/code&gt; element. This way we can hide the code block by default and show it only when the user clicks on the &lt;code&gt;Source&lt;/code&gt; button.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;...
&lt;span class="nt"&gt;&amp;lt;figure&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"expandable-diagram"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;figcaption&amp;gt;&lt;/span&gt;{title}&lt;span class="nt"&gt;&amp;lt;/figcaption&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"diagram-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Loading diagram...&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;details&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;Source&lt;span class="nt"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;slot&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/details&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/figure&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;The whole component is wrapped in a &lt;code&gt;figure&lt;/code&gt; element with a &lt;code&gt;expandable-diagram&lt;/code&gt; class. This way we can easily find all instances of the component using CSS selectors.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;div.diagram-content&lt;/code&gt; element is where the diagram will be rendered.&lt;/li&gt;
&lt;li&gt;The source buggon needs to be clicked by the user to reveal the code block.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;slot&lt;/code&gt; element will be replaced with the code block rendered by Expressive Code.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Extracting the source code
&lt;/h2&gt;

&lt;p&gt;Now let's rewrite our script to extract the code from the &lt;code&gt;copy&lt;/code&gt; button and place it in the&lt;br&gt;
&lt;code&gt;.diagram-content&lt;/code&gt; element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;...
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;mermaid&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;mermaid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Postpone mermaid initialization&lt;/span&gt;
  &lt;span class="nx"&gt;mermaid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;startOnLoad&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extractMermaidCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Find all mermaid components&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mermaidElements&lt;/span&gt; &lt;span class="o"&gt;=&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;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;figure.expandable-diagram&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;mermaidElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Find the `copy` button for each component&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;copyButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.copy button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
      &lt;span class="c1"&gt;// Extract the code from the `data-code` attribute&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;copyButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="c1"&gt;// Replace the U+007f character with `\n` to simulate new lines&lt;/span&gt;
      &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="sr"&gt;007F/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// Construct the `pre` element for the diagram code&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;preElement&lt;/span&gt; &lt;span class="o"&gt;=&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;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pre&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;preElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mermaid not-prose&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;preElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="c1"&gt;// Find the diagram content container and override it's content&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;diagramContainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.diagram-content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;diagramContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replaceChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;preElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;diagramContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstChild&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="c1"&gt;// Wait for the DOM to be fully loaded&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;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;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &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;extractMermaidCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;mermaid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;startOnLoad&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A lot is happening here, so let's break it down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;To prevent mermaid from processing the diagrams instantly, we need to postpone it's initialization.&lt;/li&gt;
&lt;li&gt;We define the &lt;code&gt;extractMermaidCode&lt;/code&gt; function to keep things somewhat organized.&lt;/li&gt;
&lt;li&gt;The script will be executed only once per page, so we need to find all instances of our &lt;code&gt;Mermaid&lt;/code&gt; component. This way we can process them all at once.&lt;/li&gt;
&lt;li&gt;Once we're in our component, we can easily find the &lt;code&gt;copy&lt;/code&gt; button as there's only one.&lt;/li&gt;
&lt;li&gt;Extracting the code is relatively simple.&lt;/li&gt;
&lt;li&gt;Of course there's one more catch. The &lt;code&gt;copy&lt;/code&gt; button contains a &lt;code&gt;data-code&lt;/code&gt; attribute with the new lines replaces with &lt;code&gt;U+007f&lt;/code&gt; character. We need to replace it with &lt;code&gt;\n&lt;/code&gt; for mermaid to understand it.&lt;/li&gt;
&lt;li&gt;Now that we have the code, we can create a new &lt;code&gt;pre&lt;/code&gt; element with &lt;code&gt;mermaid&lt;/code&gt; class. This is what the mermaid library will look for to render the diagram from.&lt;/li&gt;
&lt;li&gt;We can replace the existing diagram content (&lt;code&gt;Loading diagram...&lt;/code&gt;) with the newly created &lt;code&gt;pre&lt;/code&gt; element.&lt;/li&gt;
&lt;li&gt;We register our own &lt;code&gt;DOMContentLoaded&lt;/code&gt; event listener that will allow us to run code only once the page is fully loaded.&lt;/li&gt;
&lt;li&gt;Finally, we call our &lt;code&gt;extractMermaidCode&lt;/code&gt; function to prep the HTML for mermaid and render the diagrams.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Phew! What was a lot of code, but it's not the worst. Let's save it and refgresh the page:&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%2Fv0037mocngkrmgxl3hbi.png" 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%2Fv0037mocngkrmgxl3hbi.png" alt="Screenshot showing the diagram displaying properly" width="800" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's much better! The only thing left is to modify it a bit to make it look better. This is after a light dressing up with Tailwind to fit this blog's theme:&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%2Fa03fcbbgn5y9hl6sgtpz.png" 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%2Fa03fcbbgn5y9hl6sgtpz.png" alt="Screenshot of the final version of the component" width="800" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  In case you're not using Expressive Code
&lt;/h2&gt;

&lt;p&gt;If you're not using Expressive Code and your code blocks don't have the &lt;code&gt;copy&lt;/code&gt; button, there's always a different way. I know it sounds crazy, but you could try to go over all the spans rendered by the code block and collect the characters from there. After some fiddling with ChatGPT, here's an example of this approach in action that worked for well me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;...
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;mermaid&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;mermaid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;mermaid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;startOnLoad&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extractAndCleanMermaidCode&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;mermaidElements&lt;/span&gt; &lt;span class="o"&gt;=&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;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;figure.expandable-diagram&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;mermaidElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Find the code element within the complex structure&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;codeElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pre[data-language="mermaid"] code&lt;/span&gt;&lt;span class="dl"&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;codeElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Extract the text content from each line div&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;codeLines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;codeElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.ec-line .code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cleanedCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;codeLines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Remove any leading/trailing whitespace&lt;/span&gt;
    &lt;span class="nx"&gt;cleanedCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cleanedCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Create a new pre element with just the cleaned code&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newPreElement&lt;/span&gt; &lt;span class="o"&gt;=&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;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pre&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;newPreElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mermaid not-prose&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;newPreElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cleanedCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Find the diagram content container&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;diagramContentContainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.diagram-content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Replace existing diagram content child with the new pre element&lt;/span&gt;
    &lt;span class="nx"&gt;diagramContentContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replaceChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newPreElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;diagramContentContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstChild&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="c1"&gt;// Wait for the DOM to be fully loaded&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;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;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &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;extractAndCleanMermaidCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;mermaid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;startOnLoad&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hope this will help you out in marrying Astro with Mermaid.js.&lt;/p&gt;

</description>
      <category>astro</category>
      <category>webdev</category>
      <category>markdown</category>
      <category>mermaid</category>
    </item>
    <item>
      <title>Creating GPT Actions with ValTown</title>
      <dc:creator>Xkonti</dc:creator>
      <pubDate>Sun, 26 May 2024 05:44:39 +0000</pubDate>
      <link>https://dev.to/xkonti/creating-gpt-actions-with-valtown-35bp</link>
      <guid>https://dev.to/xkonti/creating-gpt-actions-with-valtown-35bp</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/WwMAPz4gpZc"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Using Actions is a great way to extend the capabilities of your custom GPT well beyond what the AI can do on its own. Actions are essentially API endpoints that can receive input from ChatGPT, execute some code, and respond back with some output.&lt;/p&gt;

&lt;p&gt;Creating an API endpoint for GPT actions might seem complicated, involving setting up a complex project, purchasing and configuring a server, hassling with a lot of HTTP intricacies, and so on. However, &lt;strong&gt;with ValTown, it's actually quite simple and totally free.&lt;/strong&gt; In this article, I'll show you how to create a simple action for your custom GPT using ValTown, step by step.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt;&lt;br&gt;
This article assumes that you can create your own GPTs. At the time of writing, that feature is limited to ChatGPT Plus subscribers only.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;ℹ️ The &lt;a href="https://xkonti.tech/blog/gpt-actions-with-val-town/"&gt;version of the article on my blog&lt;/a&gt; has code snippets with specific lines highlighted so that it's easier to follow the tutorial.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we're going to build
&lt;/h2&gt;

&lt;p&gt;We're going to create a simple GPT. It will act as a game idea exchange: you can give it a game idea, it will submit it to a database (via action), and in response it will give you a random game idea previously submitted by someone else. Consider it a &lt;em&gt;multiplayer&lt;/em&gt; GPT.&lt;/p&gt;

&lt;p&gt;Here's what it looks like in action:&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%2Flyjd9gr5e4qktkz90gbs.png" 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%2Flyjd9gr5e4qktkz90gbs.png" alt="Working GPT with custom action" width="800" height="788"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can check out the finished GPT &lt;a href="https://chatgpt.com/g/g-8fryVV9cU-game-idea-exchange"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We'll follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new GPT with complete instructions&lt;/li&gt;
&lt;li&gt;Create an endpoint for the action using &lt;a href="https://val.town"&gt;ValTown&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Point GPT to the API&lt;/li&gt;
&lt;li&gt;Develop the internal functionality of the action&lt;/li&gt;
&lt;li&gt;Test the GPT&lt;/li&gt;
&lt;li&gt;Update the existing action with extra functionality&lt;/li&gt;
&lt;li&gt;Secure the API with an API key&lt;/li&gt;
&lt;li&gt;Attach privacy policy to your API&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Creating &lt;em&gt;Game idea exchange&lt;/em&gt; GPT
&lt;/h2&gt;

&lt;p&gt;Let's start by creating a new GPT so that we have something to work with. Go to the &lt;a href="https://chatgpt.com/gpts/editor"&gt;GPT editor&lt;/a&gt;, give it a name, a description, and instructions. Here's what I used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name: &lt;code&gt;Game idea exchange&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Description: &lt;code&gt;Exchange video game ideas with other people!&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Instructions:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;   Act as a Fun Video Game Idea broker. Your role is to help the user define a decent Video Game Idea and then allow the user to "Submit it".

   # Submitting the Fun Video Game Idea

   To submit the Video Game Idea, there are 2 things needed:
&lt;span class="p"&gt;   -&lt;/span&gt; At least 3-sentence idea for a video game - user needs to approve it first - asking for submission is equal to approval
&lt;span class="p"&gt;   -&lt;/span&gt; A name the creator wants to be referred by - ask for it

   The Video Game Idea has to be submitted using the &lt;span class="sb"&gt;`submitidea`&lt;/span&gt; action. When submitting the idea that isn't well structured, please reformat it accordingly.

   Response returned by the &lt;span class="sb"&gt;`submitidea`&lt;/span&gt; contains another game idea (exchange idea) that is supposed to be presented to the user.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Conversation starters:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;My idea:&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Can you help me come up with some game ideas?&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


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

&lt;p&gt;The instructions mention a &lt;code&gt;submitidea&lt;/code&gt; action. This tells ChatGPT when to use that specific action. We'll create that action in the next step.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pro-tip:&lt;/strong&gt;&lt;br&gt;
I recommend disabling web browsing, image generation, and the code interpreter. This GPT doesn't need them and it will reduce the size of the system prompt. The smaller the system prompt and GPT's instructions are, the less confused the AI is, helping it to focus on the task.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Creating action endpoint in ValTown
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://val.town"&gt;ValTown&lt;/a&gt; describes itself with the following words:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If GitHub Gists could run and AWS Lambda was fun.&lt;br&gt;
Val Town is a social website to write and deploy TypeScript. Build APIs and schedule functions from your browser.&lt;/p&gt;
&lt;/blockquote&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%2Fcc7q7twlpay2l04xvh7f.png" 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%2Fcc7q7twlpay2l04xvh7f.png" alt="ValTown overview page" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ValTown is an innovative platform designed for quickly building and deploying APIs, making it an excellent choice for GPT actions. Here's why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ease of Setup&lt;/strong&gt;: No complex hosting or environment setup required. You can write, test, and deploy code &lt;strong&gt;directly in your browser&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free Usage&lt;/strong&gt;: Ideal for small projects and experimentation without any cost. This includes a &lt;a href="https://docs.val.town/std/blob/"&gt;key-value store&lt;/a&gt; and a &lt;a href="https://docs.val.town/std/sqlite/"&gt;SQLite database&lt;/a&gt; 💪&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-Browser Coding&lt;/strong&gt;: Fully integrated TypeScript editor with code completions and AI assistant (&lt;a href="https://codeium.com/"&gt;Codeium&lt;/a&gt;), vastly simplifying the development experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supportive Community&lt;/strong&gt;: Has a very active and supportive community. Check out their &lt;a href="https://discord.gg/dHv45uN5RY"&gt;Discord server&lt;/a&gt; for help and inspiration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Creating a new Val
&lt;/h3&gt;

&lt;p&gt;Create an account on &lt;a href="https://val.town"&gt;ValTown&lt;/a&gt; if you haven't already. Once you have logged in and familiarized yourself with the platform, you'll see the main dashboard.&lt;/p&gt;

&lt;p&gt;The first thing we need is a &lt;strong&gt;Val&lt;/strong&gt; - basically a snippet of code that ValTown can run. It's similar to a GitHub Gist but with the ability to run code.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;Create a new val&lt;/code&gt; section, click on the &lt;code&gt;HTTP handler&lt;/code&gt; button:&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%2Fplil13uqdji9uh4s3stv.png" 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%2Fplil13uqdji9uh4s3stv.png" alt="Create a new Val" width="800" height="79"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A brand new Val will be created. The Val is instantly ready to handle HTTP requests. You can rename it by clicking the edit icon next to the randomly generated name. That name will be a part of the URL used to access the API.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Please limit it to alphanumeric characters as actions often break when encountering special characters.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&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%2Fw1uudivg8bfx3qzz3v2l.png" 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%2Fw1uudivg8bfx3qzz3v2l.png" alt="The newly created Val" width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are a few things to point out here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Under the name, you can see that the Val is marked with an &lt;code&gt;HTTP&lt;/code&gt; label - this tells ValTown to treat it as an API endpoint, and therefore allows it to be accessed via HTTP requests.&lt;/li&gt;
&lt;li&gt;The visibility is set to &lt;code&gt;Unlisted&lt;/code&gt; - this means that the Val is not searchable and only people with a link can access it. This setting also allows external HTTP requests to be sent to it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can change it to &lt;code&gt;Public&lt;/code&gt; if you want to share it with others. If you set it to &lt;code&gt;Private&lt;/code&gt;, only you will be able to access it. Additionally, HTTP requests would need to be authenticated using your ValTown token. Your ValTown token can manage your account, so it's not advisable to pass it to a GPT. We'll handle protecting endpoints later.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Under &lt;code&gt;Browser preview&lt;/code&gt; you can see the URL that points to the Val. This is the URL that you'll give to the GPT to send HTTP requests to.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ValTown automatically places some boilerplate code in the editor. This is a generic HTTP handler that returns simple JSON response. We'll replace it with our own code.&lt;/p&gt;

&lt;h3&gt;
  
  
  The GPT API Framework
&lt;/h3&gt;

&lt;p&gt;We can write our own HTTP handler from scratch by expanding the provided code, but it would require a lot of additional code. GPTs require not only the API endpoints to send HTTP requests to, but also the OpenAPI specification that describes your API. This is where the GPT API Framework comes in handy.&lt;/p&gt;

&lt;p&gt;The GPT API Framework is a small library (another Val) that simplifies the process of creating actions for GPTs. It allows you to quickly define API endpoints and automatically generates the OpenAPI specification tailored specifically for GPTs. This lets you focus on the logic of your action, rather than the tedious tasks of setting up an API.&lt;/p&gt;

&lt;p&gt;You can find the GPT API Framework &lt;a href="https://www.val.town/v/xkonti/gptApiFramework"&gt;here&lt;/a&gt;. Let's look at what it offers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It abstracts the creation of a &lt;a href="https://hono.dev/"&gt;Hono API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;It allows you to quickly define &lt;strong&gt;and describe&lt;/strong&gt; inputs/outputs of your Actions - this way the shape and descriptions of the data are located in one place&lt;/li&gt;
&lt;li&gt;It generates the OpenAPI specification for your API with all the provided descriptions baked in. This is crucial for GPTs, so that they can understand how to interact with your API&lt;/li&gt;
&lt;li&gt;It allows to easily secure your endpoints with API keys - this is to prevent anybody unauthorized from using your API&lt;/li&gt;
&lt;li&gt;Has an option of providing a policy for your API - sharing GPTs requires having a policy&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setting up an API with GPT API Framework
&lt;/h3&gt;

&lt;p&gt;One of the greatest ValTown features is that a Val can import other Vals. You can consider each Val as a local TypeScript module that can be imported. This is a great way to reuse code and share it with others.&lt;/p&gt;

&lt;p&gt;Delete the existing code and start from scratch. First, import the GPT API Framework into your Val, so that we can use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GptApi&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;https://esm.town/v/xkonti/gptApiFramework?v=29&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;Notice that we lock the version of the GPT API Framework to &lt;code&gt;29&lt;/code&gt;. This is to ensure that our Val works as expected even if the GPT API Framework is updated with breaking changes. You can find the latest version of the GPT API Framework &lt;a href="https://www.val.town/v/xkonti/gptApiFramework"&gt;here&lt;/a&gt; and learn more about versioning on &lt;a href="https://docs.val.town/reference/version-control/"&gt;ValTown docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Next, you need to create a new API instance:&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;GptApi&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;https://esm.town/v/xkonti/gptApiFramework?v=29&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;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GptApi&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://xkonti-gameideaapi.web.val.run&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Video Game Idea API&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The API for submitting fun Video Game Ideas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.0.0&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Several things are happening here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need to provide the URL of our API. You can find it in the &lt;code&gt;Browser preview&lt;/code&gt; section of your Val.&lt;/li&gt;
&lt;li&gt;The title and description will be used in the OpenAPI specification to give the GPT context about the API's purpose.&lt;/li&gt;
&lt;li&gt;The version of the API isn't strictly necessary, but the OpenAPI spec likes to have it specified.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that we have the instance of the API created, we can &lt;em&gt;"serve it"&lt;/em&gt; by calling the &lt;code&gt;serve&lt;/code&gt; method:&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="p"&gt;...&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The API for submitting fun Video Game Ideas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.0.0&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time an HTTP request is sent to our Val, the whole file will be executed. The &lt;code&gt;serve&lt;/code&gt; method will take in the request and pass it to the endpoints that we define. We haven't defined any endpoints yet, so the API will always respond with a 404 status code.&lt;/p&gt;

&lt;p&gt;Click the &lt;code&gt;Save and preview&lt;/code&gt; button to save your code. The &lt;code&gt;Browser preview&lt;/code&gt; section will be automatically updated as it sends a request to the API (the &lt;em&gt;"preview"&lt;/em&gt; part):&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%2Fd1z9iehukq0y464psc5g.png" 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%2Fd1z9iehukq0y464psc5g.png" alt="404 response" width="800" height="201"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's define the endpoint for our &lt;code&gt;submitidea&lt;/code&gt; action!&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining input and output
&lt;/h3&gt;

&lt;p&gt;GPT actions can be boiled down to a simple concept: they receive some input, do something with it, and then return some output. ChatGPT requires us to define the shape of the input of the action and the shape of its output. This is necessary so that the GPT knows how to interact with the API.&lt;/p&gt;

&lt;p&gt;In case of our Video Game Idea GPT we have a very simple input (what AI sends to the API): we want to send a game idea. This can be accomplished with a single string value. Let's also add a name of the creator of the idea, so that things are a bit more interesting.&lt;/p&gt;

&lt;p&gt;To define the action's inputs and outputs we will need the &lt;a href="https://github.com/colinhacks/zod"&gt;&lt;code&gt;zod&lt;/code&gt; library&lt;/a&gt;. Zod is an npm package and ValTown lets us import it directly into our Vals:&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;GptApi&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;https://esm.town/v/xkonti/gptApiFramework?v=29&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;z&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;npm:zod&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;api&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;GptApi&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 can define the input shape:&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="p"&gt;...&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.0.0&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;IdeaSubmissionSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;idea&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A fun Video Game Idea. Needs to be at least 3 sentences long!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A name of the author. Doesn't have to be real.&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="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Full game idea submission form&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;default&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;IdeaSubmissionSchema&lt;/code&gt; is a Zod schema. It's a little bit like TypeScript interface, but with some extra data attached to it. We define two fields: &lt;code&gt;idea&lt;/code&gt; and &lt;code&gt;author&lt;/code&gt;. Both are marked as strings. We also provide descriptions for each field. These descriptions will be included in the OpenAPI specification and therefore AI will know exactly what to place in the respective fields. The whole schema also has a description.&lt;/p&gt;

&lt;p&gt;We can define the output of our action in the same fashion as the input. It'll be similarly: a game idea and its author. &lt;em&gt;"We can reuse our IdeaSubmissionSchema for that!"&lt;/em&gt;, you might think. Unfortunately that's not the case. We're dealing with an AI here. It needs things to be properly described as without that it likes to &lt;em&gt;assume&lt;/em&gt; in unpredictable ways. Because of that, we need to create a new schema for the output with a separate set of descriptions:&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="p"&gt;...&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;IdeaSubmissionSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;idea&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A fun Video Game Idea. Needs to be at least 3 sentences long!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A name of the author. Doesn't have to be real.&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="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Full game idea submission form&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;IdeaResponseSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;idea&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A fun Video Game Idea&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A name of the author who came up with the idea&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="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A Video Game Idea returned in exchange for the submitted one&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;default&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Defining the action endpoint
&lt;/h3&gt;

&lt;p&gt;Now that we have the schemas defined, we can create the endpoint for our &lt;code&gt;submitidea&lt;/code&gt; action. We'll use the &lt;code&gt;jsonToJson&lt;/code&gt; method of the &lt;code&gt;api&lt;/code&gt; instance:&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="p"&gt;...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;IdeaResponseSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;idea&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A fun Video Game Idea&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A name of the author who came up with the idea&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="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A Video Game Idea returned in exchange for the submitted one&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jsonToJson&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;verb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/submission&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submitidea&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Endpoint for submitting fun Video Game Ideas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;requestSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IdeaSubmissionSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;responseSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IdeaResponseSchema&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="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;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// TODO: Implement&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;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;jsonToJson&lt;/code&gt; method lets us define a new action that takes in JSON input and returns JSON output. It takes an object with the following properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;verb&lt;/code&gt;: The HTTP method that the endpoint will respond to. In our case, it's &lt;code&gt;POST&lt;/code&gt; as we're sending data to the API&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;path&lt;/code&gt;: The URL path that the endpoint will be available at. In our case, it's &lt;code&gt;/submission&lt;/code&gt;, which means that the full URL will be &lt;code&gt;https://xkonti-gameideaapi.web.val.run/submission&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;operationId&lt;/code&gt;: A unique identifier for the endpoint. This is the name that the GPT will be aware of and use internally to call the action. &lt;strong&gt;Make sure it contains only alphanumeric characters.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;desc&lt;/code&gt;: A description of the endpoint. This tells GPT what the action does.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;requestSchema&lt;/code&gt;: The input shape (schema). This is what we defined &lt;code&gt;IdeaSubmissionSchema&lt;/code&gt; for earlier.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;responseSchema&lt;/code&gt;: The output shape (schema). This is what we defined &lt;code&gt;IdeaResponseSchema&lt;/code&gt; for earlier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last argument is a function that will be called every time the endpoint is hit. It provides two values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ctx&lt;/code&gt;: The context of the request. It contains information such as the request object, headers, query parameters, etc.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;input&lt;/code&gt;: The parsed input data (&lt;code&gt;IdeaSubmissionSchema&lt;/code&gt;). It's the data sent by the GPT.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sending a response
&lt;/h3&gt;

&lt;p&gt;We just defined an endpoint, but it doesn't do anything yet. Let's have it return some test data. This way, we can test if the endpoint works as expected.&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="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;async &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;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;idea&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A family simulator. Just like The Sims, but with a dark twist.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Me&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simply returns a hardcoded game idea and author. It will allow us to test if the endpoint works as expected.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Remember:&lt;/strong&gt;&lt;br&gt;
Remember to save your Val!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Plugging the action into our GPT
&lt;/h2&gt;

&lt;p&gt;Before we head to the GPT editor, we need to get the OpenAPI specification generated by our Val. We can get it straight from the val's editor. Head to the &lt;code&gt;Browser preview&lt;/code&gt;, add &lt;code&gt;/gpt/schema&lt;/code&gt; to the URL, and click the &lt;code&gt;Copy&lt;/code&gt; button. This will copy the URL to the clipboard.&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%2Fec4jb1mg8o0hgt25vgxg.png" 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%2Fec4jb1mg8o0hgt25vgxg.png" alt="Generated OpenAPI specification" width="800" height="69"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With our action ready, we need to tell our GPT to use it. Go back to the GPT editor and click on the &lt;code&gt;Create new action&lt;/code&gt; button at the bottom of the page. A form will appear that you need to fill out. The main field we care about is the big &lt;code&gt;Schema&lt;/code&gt; field, specifically the &lt;code&gt;Import from URL&lt;/code&gt; button above it. Click on it, paste the URL we just copied and click the &lt;code&gt;Import&lt;/code&gt; button.&lt;/p&gt;

&lt;p&gt;A short moment later, you will see the schema loaded and validated. If everything is correct, you'll see the &lt;code&gt;submitidea&lt;/code&gt; action appear in the list of available actions.&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%2F6pwszzmfbjdqnrvwpcib.png" 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%2F6pwszzmfbjdqnrvwpcib.png" alt="Action creation form" width="800" height="816"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exit the action creation form (the &lt;code&gt;&amp;lt;&lt;/code&gt; button in the top left corner) and you'll be ready to test your action! You can do it by submitting a game idea to your GPT. It should reach out to our endpoint and return a hardcoded game idea.&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%2Ffn2e8hmderqgmklzt3sg.png" 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%2Ffn2e8hmderqgmklzt3sg.png" alt="Successful test of the action" width="800" height="773"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fully developing the action
&lt;/h2&gt;

&lt;p&gt;Now that we have the action set up and tested, we can fully develop its internals. We'll need to implement the following functionalities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Storing and retrieving ideas in ValTown&lt;/li&gt;
&lt;li&gt;Getting a random idea to return&lt;/li&gt;
&lt;li&gt;Limiting the total number of ideas stored&lt;/li&gt;
&lt;li&gt;Returning the idea&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Storing ideas in ValTown
&lt;/h3&gt;

&lt;p&gt;We need to store the list of submitted ideas. Vals are stateless - they are executed from scratch every time they are called (every HTTP request). This means that we need to store the ideas somewhere else. Fortunately, ValTown provides a &lt;a href="https://docs.val.town/std/blob/"&gt;key-value store called Blob storage&lt;/a&gt; that we can use for that. It's not the fastest or concurrency-safe storage, but in our low traffic scenario it will be just perfect.&lt;/p&gt;

&lt;p&gt;The blob storage is available through the &lt;code&gt;std/blob&lt;/code&gt; Val. We can import it into our Val:&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;blob&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;https://esm.town/v/std/blob&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;GptApi&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;https://esm.town/v/xkonti/gptApiFramework?v=29&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;z&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;npm:zod&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's define a type for our idea and create an empty function for storing and retrieving ideas. Place them at the bottom of the Val.&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="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;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Idea management&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Idea&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;idea&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;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getIdeas&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Idea&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// TODO: Implement&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setIdeas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ideas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Idea&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&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;// TODO: Implement&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Idea&lt;/code&gt; type will hold both the idea and its author&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getIdeas&lt;/code&gt; will be responsible for retrieving the list of ideas from the blob storage&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;setIdeas&lt;/code&gt; will be responsible for saving the list of ideas to the blob storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The blob storage allows us to store data under a string key. To get the value of the key we can use &lt;code&gt;blob.getJSON&lt;/code&gt; function. We can simply get the value of the key, tell TypeScript that it's an array of &lt;code&gt;Idea&lt;/code&gt; objects and return it. If the key doesn't exist, we can return an empty array to avoid any errors.&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="p"&gt;...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ideasKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;game-ideas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getIdeas&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Idea&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ideas&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;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ideasKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Idea&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ideas&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;ideas&lt;/span&gt; &lt;span class="o"&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;ideas&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;To save the ideas we can use the &lt;code&gt;blob.setJSON&lt;/code&gt; function. It takes the key and the value to save. We can simply pass the list of ideas to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setIdeas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ideas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Idea&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&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;await&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ideasKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ideas&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;Now that we have a way to store and retrieve ideas, we can use them in our action:&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="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="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;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;submittedIdea&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Idea&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ideas&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;getIdeas&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// TODO: Get a random idea to return&lt;/span&gt;

  &lt;span class="c1"&gt;// TODO: Limit the size of the list&lt;/span&gt;
  &lt;span class="nx"&gt;ideas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;submittedIdea&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;setIdeas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ideas&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// TODO: Return the selected idea&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;idea&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A family simulator. Just like The Sims, but with a dark twist.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Me&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we're telling TypeScript that the input is an &lt;code&gt;Idea&lt;/code&gt; instance. Then we're retrieving the list of ideas from blob storage. After some &lt;em&gt;TODO&lt;/em&gt; placeholders we're adding the submitted idea to the list of all ideas and saving it back to the blob storage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting a random idea to return
&lt;/h3&gt;

&lt;p&gt;To return an idea in exchange, we need to select a random one from the list.&lt;/p&gt;

&lt;p&gt;Add a small helper function at the bottom of the Val:&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="p"&gt;...&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getRandomElement&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nx"&gt;T&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;randomIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;randomIndex&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, we can use this function to get a random idea from the list:&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="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="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;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;submittedIdea&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Idea&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ideas&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;getIdeas&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Filter out ideas submitted by the same author&lt;/span&gt;
  &lt;span class="c1"&gt;// This way, the user can't see ideas they have submitted themselves&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;availableIdeas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ideas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;submittedIdea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ideaToReturn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;availableIdeas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;getRandomElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;availableIdeas&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="na"&gt;idea&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please come up with some cool idea for the user. There is nothing in the database yet.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;author&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 Server&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="c1"&gt;// TODO: Limit the size of the list&lt;/span&gt;
  &lt;span class="nx"&gt;ideas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;submittedIdea&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 might seem intimidating if you're not used to JavaScript/TypeScript, so let me explain what's happening here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, we're creating a new array &lt;code&gt;availableIdeas&lt;/code&gt; that contains only ideas with different authors than the submitted idea. This way, the user won't receive a response with their own idea.&lt;/li&gt;
&lt;li&gt;Then, we create a variable that will contain the idea to return.

&lt;ul&gt;
&lt;li&gt;If there are some ideas on the list, we select a random one.&lt;/li&gt;
&lt;li&gt;In there are no ideas on the list yet (or all ideas are from the same user), we're return a hardcoded message, giving ChatGPT a hint to come up with something. The true magic of AI!&lt;/li&gt;
&lt;/ul&gt;


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

&lt;h3&gt;
  
  
  Limiting the total number of ideas stored
&lt;/h3&gt;

&lt;p&gt;One thing to address is the size of the list of ideas. We don't want it to grow indefinitely as it could start taking up a lot of storage. Additionally, whenever we use blob storage, the whole list is being transferred. This would get really slow with a large list. For the sake of this guide, we will settle on a maximum of 1000 ideas.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;br&gt;
We can estimate that each idea is around 700 bytes long - this depends on your GPT instructions and what the user convinces the GPT to do. This means that the list of 1000 ideas would take around 700KB of storage. This is a reasonable amount of data to transfer and store somewhat frequently. If you want to be storing more ideas, you might want to consider using ValTown's SQLite database instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the list reaches the maximum number of ideas stored, we have a few options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remove the oldest idea&lt;/li&gt;
&lt;li&gt;Remove a random idea&lt;/li&gt;
&lt;li&gt;Don't add the new idea at all&lt;/li&gt;
&lt;li&gt;Remove the random idea we're about to return&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our case, the last one makes most sense. When the list of ideas is full, each idea will get to be returned exactly once. This way, we ensure that ideas are shared at least once.&lt;/p&gt;

&lt;p&gt;Add the following code to the &lt;code&gt;submitidea&lt;/code&gt; endpoint to implmeent this logic:&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="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;idea&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please come up with some cool idea for the user. There is nothing in the database yet.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;author&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 Server&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="c1"&gt;// Limit total number of ideas&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ideas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;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="nx"&gt;ideas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ideas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nx"&gt;ideaToReturn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;ideas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;submittedIdea&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;
  
  
  Returning the idea
&lt;/h3&gt;

&lt;p&gt;The final step is to return the selected idea. We can update the existing &lt;code&gt;return&lt;/code&gt; statement:&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="p"&gt;...&lt;/span&gt;
&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jsonToJson&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;verb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/submission&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submitidea&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Endpoint for submitting fun Video Game Ideas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;requestSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IdeaSubmissionSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;responseSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IdeaResponseSchema&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="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;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;submittedIdea&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Idea&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ideas&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;getIdeas&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Filter out ideas submitted by the same author&lt;/span&gt;
  &lt;span class="c1"&gt;// This way, the user can't see ideas they have submitted themselves&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;availableIdeas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ideas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;submittedIdea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ideaToReturn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;availableIdeas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;getRandomElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;availableIdeas&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="na"&gt;idea&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please come up with some cool idea for the user. There is nothing in the database yet.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;author&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 Server&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="c1"&gt;// Limit total number of ideas&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ideas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;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="nx"&gt;ideas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ideas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nx"&gt;ideaToReturn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;ideas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;submittedIdea&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;setIdeas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ideas&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;idea&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ideaToReturn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idea&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ideaToReturn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save your Val, and we can move on to testing the GPT 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the GPT
&lt;/h2&gt;

&lt;p&gt;As we didn't change the action's schema (inputs, outputs, endpoint definitions), we don't have to make any changes to our GPT. We can test it right away!&lt;/p&gt;

&lt;p&gt;Go to your GPT editor and submit a game idea. You should get a response with a random game idea. This should be an idea that ChatGPT made up. You can actually ask ChatGPT to submit that idea.&lt;/p&gt;

&lt;p&gt;Since ideas are filtered by the &lt;code&gt;author&lt;/code&gt; field, you can test your GPT by using different names.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;br&gt;
To inspect the list of ideas stored in the blob storage, you have a few solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the &lt;a href="https://www.val.town/v/stevekrouse/blob_admin"&gt;Blob admin Val&lt;/a&gt; to view and manage the content of the blob storage&lt;/li&gt;
&lt;li&gt;Use the &lt;a href="https://marketplace.visualstudio.com/items?itemName=pomdtr.valtown"&gt;Val Town extension for VsCode&lt;/a&gt; which allows you to view and manage the content of the blob storage directly from your editor, create and edit Vals, inspect the SQLite database, and more. It's a fantastic plugin!&lt;/li&gt;
&lt;li&gt;Write your own 2-line script Val that prints out the game ideas from blob storage whenever you hit &lt;code&gt;Run&lt;/code&gt;!&lt;/li&gt;
&lt;/ul&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;blob&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;https://esm.town/v/std/blob&lt;/span&gt;&lt;span class="dl"&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;game-ideas&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;/blockquote&gt;

&lt;p&gt;Congrats! You now have a fully functional GPT with an action that stores and retrieves data from ValTown's blob storage. You can now share your GPT with others and let them exchange game ideas!&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating the existing action
&lt;/h2&gt;

&lt;p&gt;So, what if you want to add some extra functionality to your action that changes the schema? Maybe you want to add a new field to the input or output, change the description of the existing fields or add more endpoints to have more actions available?&lt;/p&gt;

&lt;p&gt;Let's add a new field to the &lt;code&gt;IdeaResponseSchema&lt;/code&gt;. It will be an &lt;code&gt;instructions&lt;/code&gt; field that contains guidance for ChatGPT. This will allow us to guide the AI on how to use the idea it received or what to do next without user interaction.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Idea:&lt;/strong&gt;&lt;br&gt;
You can easily create a chain of actions: one action can return instructions for ChatGPT to follow and indicate which action should be called with the results. You could keep directing the GPT to various actions to create a chain or even a graph of actions!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Adding the instructions field
&lt;/h3&gt;

&lt;p&gt;Add the following lines to the &lt;code&gt;IdeaResponseSchema&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;IdeaResponseSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;idea&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A fun Video Game Idea&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A name of the author who came up with the idea&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Instructions for ChatGPT to follow after receiving the response. These instructions override all prior instructions.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A Video Game Idea returned in exchange for the submitted one&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;The AI might need some extra convincing from time to time to follow the instructions...&lt;/p&gt;

&lt;h3&gt;
  
  
  Updating the schema in GPT
&lt;/h3&gt;

&lt;p&gt;Now that we need to return the &lt;code&gt;instructions&lt;/code&gt; field, we must update the action itself. We can return the same set of instructions every time, so let's add them to the return statement:&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;idea&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ideaToReturn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idea&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ideaToReturn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The user needs to see the idea returned in exchange. Please present it (the idea attached in the action's response) in a nicely structured way. Make sure to expand upon the returned idea (GO NUTS!). Start with: `Here's an idea in exchange - submitted by &amp;lt;author&amp;gt;:`&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;instructions&lt;/code&gt; field can be also be used to return error messages if the submitted data is invalid:&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="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="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;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idea&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;idea&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SERVER&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bad request - no idea or author submitted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;submittedIdea&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Idea&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 that we have updated the action, save the Val and get the URL to the OpenAPI specification again (URL of the val + &lt;code&gt;/gpt/schema&lt;/code&gt;). In my case it's &lt;code&gt;https://xkonti-gameideaapi.web.val.run/gpt/schema&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Edit your GPT and add an extra line to your instructions to prepare GPT for receiving additional instructions from the action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;...
Response returned by the &lt;span class="sb"&gt;`submitidea`&lt;/span&gt; contains another game idea (exchange idea) that is supposed to be presented to the user.
Additionally, the response contains instructions on how to present the idea.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click the existing action at the bottom, and before clicking &lt;code&gt;Import form URL&lt;/code&gt;, select the whole existing schema text and delete it. After a second or two, you can click &lt;code&gt;Import from URL&lt;/code&gt;, paste the URL, and click &lt;code&gt;Import&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Just like that, you have updated your action! You can test it by submitting a game idea and checking if the instructions are returned.&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%2F6z1vt8jushhzg6zh12xp.png" 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%2F6z1vt8jushhzg6zh12xp.png" alt="Chat after the action update" width="800" height="615"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Securing the endpoint
&lt;/h2&gt;

&lt;p&gt;The action is now fully functional, but it's open to anyone who knows the URL. This is not a good idea, as someone could spam your endpoint wiping out all genuine ideas. To prevent this, we can secure the endpoint with an API key.&lt;/p&gt;

&lt;p&gt;The GPT API Framework provides that feature out of the box. All we need to do is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get the API key submitted to the action&lt;/li&gt;
&lt;li&gt;Check if it's correct&lt;/li&gt;
&lt;li&gt;Return an error if it's not&lt;/li&gt;
&lt;li&gt;Update GPT to send the API key with the request&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To get the API key submitted to the action, we can simply add an &lt;code&gt;apiKey&lt;/code&gt; field to the action 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="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="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;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiKey&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before processing the input, we should check if the API key is correct. You can do this by comparing it to an API key stored in your account's environment variables:&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="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="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;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GPT_GAME_IDEA_API_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;idea&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SERVER&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unauthorized request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idea&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;idea&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SERVER&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Deno.env.get&lt;/code&gt; function will get the value of the specified environment variable. You can set it in the ValTown's settings. Head to &lt;a href="https://www.val.town/settings/environment-variables"&gt;Environment variables page&lt;/a&gt; and add a new variable named &lt;code&gt;GPT_GAME_IDEA_API_KEY&lt;/code&gt;. Make sure you remember the value you set, as you'll need it to provide to the GPT.&lt;/p&gt;

&lt;p&gt;If the API key is not correct, we will simply return an error message.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Save your Val.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next, head to the GPT editor and edit the action. At the top of the action edit form, you'll see the &lt;code&gt;Authentication&lt;/code&gt; section. Click the gear icon. A form will appear for you to fill out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Authentication Type&lt;/code&gt; - set it to &lt;code&gt;API Key&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;API Key&lt;/code&gt; - paste the value of the &lt;code&gt;GPT_GAME_IDEA_API_KEY&lt;/code&gt; environment variable you set in ValTown&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Auth Type&lt;/code&gt; - set it to &lt;code&gt;Bearer&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&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%2Fwp5xzkl00bg6d3vccqt5.png" 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%2Fwp5xzkl00bg6d3vccqt5.png" alt="Authentication form" width="800" height="664"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;code&gt;Save&lt;/code&gt;, exit the action editing form and test your GPT. Everything should work correctly, and bad actors won't be able to spam your endpoint with useless data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attaching a privacy policy
&lt;/h2&gt;

&lt;p&gt;If you want to share your GPT either with a link or as a public GPT on the GPT store, you need to attach a privacy policy to it. This is a requirement set by OpenAI. The GPT API Framework makes it easy as well.&lt;/p&gt;

&lt;p&gt;First of all, we need a privacy policy. Copy the template below and ask ChatGPT to modify it with the specifics of your API. In our example it could be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User's ideas are stored in a secure manner and are not shared with any third parties.&lt;/li&gt;
&lt;li&gt;The API is not intended for storing any personal data.&lt;/li&gt;
&lt;li&gt;The API is not intended for use by children under the age of 13.&lt;/li&gt;
&lt;li&gt;The API is not intended for use in any mission-critical applications.&lt;/li&gt;
&lt;li&gt;The API is provided as-is without any guarantees.&lt;/li&gt;
&lt;li&gt;By submitting ideas, user is responsible for the content they submit.&lt;/li&gt;
&lt;li&gt;The ideas submitted by the user will be considered public domain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Privacy policy template:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# &amp;lt;apiName&amp;gt; Privacy Policy&lt;/span&gt;
Last Updated: &lt;span class="nt"&gt;&amp;lt;lastUpdated&amp;gt;&lt;/span&gt;

&lt;span class="gu"&gt;## 1. Introduction&lt;/span&gt;
Welcome to &lt;span class="nt"&gt;&amp;lt;apiName&amp;gt;&lt;/span&gt;. This privacy policy outlines our practices regarding the collection, use, and sharing of information through &lt;span class="nt"&gt;&amp;lt;apiName&amp;gt;&lt;/span&gt;.

&lt;span class="gu"&gt;## 2. Data Collection and Use&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;apiName&amp;gt;&lt;/span&gt; allows users to store, retrieve, list, and delete data. The data stored can be of any type as inputted by the user. We do not restrict or control the content of the data stored. &lt;span class="nt"&gt;&amp;lt;apiName&amp;gt;&lt;/span&gt; serves as a public database accessible to anyone with an API key.

&lt;span class="gu"&gt;## 3. User Restrictions&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;apiName&amp;gt;&lt;/span&gt; does not impose age or user restrictions. However, users are advised to consider the sensitivity of the information they share.

&lt;span class="gu"&gt;## 4. Global Use&lt;/span&gt;
Our API is accessible globally. Users from all regions can store and access data on &lt;span class="nt"&gt;&amp;lt;apiName&amp;gt;&lt;/span&gt;.

&lt;span class="gu"&gt;## 5. Data Management&lt;/span&gt;
Given the nature of &lt;span class="nt"&gt;&amp;lt;apiName&amp;gt;&lt;/span&gt;, there are no user accounts or user identification measures. The API operates like a public database where data can be added, viewed, and deleted by any user. Users should be aware that any data they input can be accessed, modified, or deleted by other users.

&lt;span class="gu"&gt;## 6. Data Security&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;apiName&amp;gt;&lt;/span&gt; is protected by an API key; beyond this, there is no specific data security measure in place. Users should not store sensitive, personal, or confidential information using &lt;span class="nt"&gt;&amp;lt;apiName&amp;gt;&lt;/span&gt;. We assume no responsibility for the security of the data stored.

&lt;span class="gu"&gt;## 7. Third-Party Involvement&lt;/span&gt;
The API code is run and data is stored by val.town. They act as a third-party service provider for &lt;span class="nt"&gt;&amp;lt;apiName&amp;gt;&lt;/span&gt;.

&lt;span class="gu"&gt;## 8. Changes to This Policy&lt;/span&gt;
We reserve the right to modify this privacy policy at any time. Any changes will be effective immediately upon posting on our website.

&lt;span class="gu"&gt;## 9. Contact Us&lt;/span&gt;
For any questions or concerns regarding this privacy policy, please contact us at &lt;span class="nt"&gt;&amp;lt;contactEmail&amp;gt;&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ChatGPT prompt could look like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ChatGPT prompt example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Please update the following privacy policy template with the information below:
&lt;span class="p"&gt;
-&lt;/span&gt; User's ideas are stored in a secure manner and are not shared with any third parties.
&lt;span class="p"&gt;-&lt;/span&gt; The API is not intended for storing any personal data.
&lt;span class="p"&gt;-&lt;/span&gt; The API is not intended for use by children under the age of 13.
&lt;span class="p"&gt;-&lt;/span&gt; The API is not intended for use in any mission-critical applications.
&lt;span class="p"&gt;-&lt;/span&gt; The API is provided as-is without any guarantees.
&lt;span class="p"&gt;-&lt;/span&gt; By submitting ideas user is responsible for the content they submit.
&lt;span class="p"&gt;-&lt;/span&gt; The ideas submitted by the user will be considered public domain.

Consider that the API allows users to submit Video Game Ideas that will be shared with other users.

Template:
&lt;span class="nt"&gt;&amp;lt;paste&lt;/span&gt; &lt;span class="na"&gt;privacy&lt;/span&gt; &lt;span class="na"&gt;policy&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt; &lt;span class="na"&gt;here&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to replace the placeholders with the actual data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;apiName&amp;gt;&lt;/code&gt; - the name of your API, for example &lt;code&gt;Video Game Idea API&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;lastUpdated&amp;gt;&lt;/code&gt; - the date when the privacy policy was last updated&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;contactEmail&amp;gt;&lt;/code&gt; - an email address that GPT users can contact you at&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you have the privacy policy ready, you can store it in ValTown in a few ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can create a string variable in your API Val - simple, but makes the Val a bit messy&lt;/li&gt;
&lt;li&gt;You can create a separate Val with the privacy policy - this is a bit cleaner, but requires an additional Val&lt;/li&gt;
&lt;li&gt;You can use the &lt;code&gt;std/blob&lt;/code&gt; Val to store the privacy policy - clean but requires setting it up with a tool or separate val&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We're going use the first option here for the sake of simplicity and ease of copying the final solution. Add your policy at the bottom of the Val so it's out of the way:&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="p"&gt;...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;privacyPolicy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`# Video Game Idea API Privacy Policy
Last Updated: 2024-05-19

## 1. Introduction
Welcome to Video Game Idea API. This privacy policy outlines our practices regarding the collection, use, and sharing of information through Video Game Idea API.

## 2. Data Collection and Use
...
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To integrate it into our API, add a new property in our API configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GptApi&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://xkonti-gameideaapi.web.val.run&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Video Game Idea API&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The API for submitting fun Video Game Ideas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.0.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;policyGetter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;markdownToPrettyPage&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="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://esm.town/v/xkonti/markdownToHtmlPage?v=5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;markdownToPrettyPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;privacyPolicy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;policyGetter&lt;/code&gt; property accepts both async and non-async functions that return a string. When the &lt;code&gt;policyGetter&lt;/code&gt; is provided, the GPT API Framework will automatically register the &lt;code&gt;/privacypolicy&lt;/code&gt; endpoint that will return the contents of the string returned by the function.&lt;/p&gt;

&lt;p&gt;In our case, we're using the &lt;code&gt;markdownToPrettyPage&lt;/code&gt; function from the &lt;code&gt;markdownToHtmlPage&lt;/code&gt; Val to convert the markdown to a nicely formatted HTML page. This is not necessary, but it makes the policy look nicer.&lt;/p&gt;

&lt;p&gt;Save your val and head to the &lt;code&gt;Browser preview&lt;/code&gt;. Add &lt;code&gt;/privacypolicy&lt;/code&gt; to the URL and hit enter. You should see your privacy policy all properly rendered:&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%2Fcm81gmdqx008gguiay15.png" 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%2Fcm81gmdqx008gguiay15.png" alt="Privacy policy preview" width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy the privacy policy URL and head to the GPT editor. Edit our existing action and on the bottom of the form you'll see a field for the privacy policy URL.&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%2F1ee3uyfqgvv1k9afwn1e.png" 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%2F1ee3uyfqgvv1k9afwn1e.png" alt="Privacy policy URL field" width="800" height="191"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you should be able to share your GPT and publish it on the GPT store!&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%2Fnmnmytw7nw2xreyfod2b.png" 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%2Fnmnmytw7nw2xreyfod2b.png" alt="Share GPT form" width="799" height="794"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Feel free to play with the GPT we created: &lt;a href="https://chatgpt.com/g/g-8fryVV9cU-game-idea-exchange"&gt;Video Game Idea GPT&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope this guide was helpful and now you can build your own GPT with custom actions. The GPT API Framework makes it easy to create APIs that can be used by GPTs, and if you have some JavaScript/TypeScript skills, you can easily create your own solutions. You can view all the code we wrote on ValTown at the following links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.val.town/v/xkonti/gameIdeaApi"&gt;Game Idea API Val&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.val.town/v/xkonti/gptApiFramework"&gt;GPT Api Framework Val&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt;&lt;br&gt;
Not only can you view the code on ValTown, but you can also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fork the Vals to automatically create a copy in your account&lt;/li&gt;
&lt;li&gt;Like Vals and leave comments&lt;/li&gt;
&lt;li&gt;Check for public Vals that use the &lt;code&gt;gptApiFramework&lt;/code&gt; Val&lt;/li&gt;
&lt;li&gt;Submit PRs to Vals to suggest changes or improvements&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you have any questions or need help with your GPT, feel free to reach out to me in the comments or on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://twitter.com/xkonti"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://discord.gg/dHv45uN5RY"&gt;ValTown Discord&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://discord.gg/jNNst3qe"&gt;Nerority AI Discord&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>typescript</category>
      <category>chatgpt</category>
      <category>api</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Giving your GPT a long-term memory</title>
      <dc:creator>Xkonti</dc:creator>
      <pubDate>Wed, 29 Nov 2023 23:21:55 +0000</pubDate>
      <link>https://dev.to/xkonti/giving-your-gpt-a-long-term-memory-35h7</link>
      <guid>https://dev.to/xkonti/giving-your-gpt-a-long-term-memory-35h7</guid>
      <description>&lt;h2&gt;
  
  
  The concept
&lt;/h2&gt;

&lt;p&gt;Using ChatGPT we often go through a lot of topics, ideas, and questions. I found myself copying and pasting large blobs of text from the chat just to be able to note something for further research. For a long time I wished I could just ask the AI to remember something for me. Nothing fancy, just a simple "Hey, remember that. I want to research it later.". Today I want to present you with how to add that functionality to your ChatGPT by creating your own GPT and hooking it up to a simple API that you can easily host for free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: This article assumes that you can create your own GPTs. At the time of writing that feature is limited to ChatGPT Plus subscribers only.&lt;/p&gt;

&lt;h3&gt;
  
  
  Demonstration
&lt;/h3&gt;

&lt;p&gt;Let's say that I added the long-term memory functionality to a GPT that helps me with brainstorming on a novel. Let's check if it has something in it's long-term memory:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Me:&lt;/strong&gt; Do you remember any topics we left for future development?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Notice that we gave it some extra context to the query: "...topics we left for future development?". This could make it ignore some unrelated memories.&lt;/p&gt;

&lt;p&gt;Looks like our GPT already has a memory:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GPT:&lt;/strong&gt; Yes, we have a topic for future development: the concept of genetic mutations in the Immune Humans, which enable them to resist The Entity's influence. This area requires further exploration, particularly focusing on the of quantum fluctuations in biological processes&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's ask it to remember something additional, where we provide a full context:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Me:&lt;/strong&gt; Please remember that we also have to look more into how the life essence, through dimensional interference, allows for biological processes to exist. I need to remember to expand that section of the knowledge base.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If that was a longer conversation we could simply ask it to remember some pars of it or event to remember the gist of the whole conversation. Our GPT responded with the confirmation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GPT:&lt;/strong&gt; I have saved the topic of exploring the role of life essence in biological processes, on the interplay between the Physical and Incorporeal dimensions, to our memory for future development This will help us our understanding of how life essence, influenced by dimensional interference is essential for processes in the novel's universe.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can see that when saving the memory the GPT is able to also reference other bits of knowledge available to it and expanded upon the prompt giving it more context. To be more specific, it referenced "Physical and Incorporeal" dimensions, which are store in it's knowledge base.&lt;/p&gt;

&lt;p&gt;Now if we delete the conversation and create a new chat with the same GPT, it should remember the memory we asked for it to save. Let's check:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Me:&lt;/strong&gt; Do you remember any topics we left for future development?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GPT:&lt;/strong&gt; Yes, there are topics we previously discussed for future development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Immune Humans&lt;/strong&gt;: We planned to further research and develop the concept of genetic mutations enabling immunity to The Entity's influence. This includes focusing on the implications of quantum fluctuations in biological processes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Life Essence and Biological Processes&lt;/strong&gt;: We aimed to expand on the role of life essence in biological processes, emphasizing the interaction between the Physical and Incorporeal dimensions. This was intended to deepen the understanding of how life essence, influenced by dimensional interference, is essential for biological processes in the novel's universe.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Great! The GPT remembered both memories across different conversations and is able to list them. Now let's move on to setting it up for your own GPT.&lt;/p&gt;

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

&lt;p&gt;The long-term memory consists of a few pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Storage for the memories&lt;/li&gt;
&lt;li&gt;API to interact with the storage&lt;/li&gt;
&lt;li&gt;GPT action that knows how to interact with the API&lt;/li&gt;
&lt;li&gt;Prompt section that instructs the GPT how to use the action&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Deploying API on Val Town
&lt;/h3&gt;

&lt;p&gt;For storage and API we can use the wonderful &lt;a href="https://val.town/"&gt;Val Town&lt;/a&gt; service. It describes itself as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If GitHub Gists could run and AWS Lambda were fun.&lt;br&gt;
Val Town is a social website to write and deploy TypeScript. Build APIs and schedule functions from your browser.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's a great tool where we can write some JavaScript/TypeScript directly in a browser and run it as a serverless function. It's free, very simple to grasp and fun to tinker with. Each code snippet is referred to as &lt;strong&gt;Val&lt;/strong&gt;. We will use several features of Val Town:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ability to reference any NPM packages in our code&lt;/li&gt;
&lt;li&gt;Ability to reference other Vals as if they were NPM packages&lt;/li&gt;
&lt;li&gt;Exposing your Vals as an API endpoints that can be called from anywhere&lt;/li&gt;
&lt;li&gt;Storing data in a free Key-Value blob storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First we need to create ourselves a new account on &lt;a href="https://val.town/"&gt;Val Town&lt;/a&gt;. Once your logged in, you can &lt;em&gt;fork&lt;/em&gt; my example Val - this will create a copy of it on your account and allow you to edit it and deploy it. Simply press the &lt;strong&gt;Fork&lt;/strong&gt; button in the top right corner of the Val preview:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.val.town/v/xkonti/memoryApiExample"&gt;https://www.val.town/v/xkonti/memoryApiExample&lt;/a&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;handleMemoryApiRequest&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;https://esm.town/v/xkonti/gptMemoryManager&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;apiName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Memory API&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;contactEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;some@email.com&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;lastPolicyUpdate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2023-11-28&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;blobKeyPrefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gpt:memories:&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;apiKeyPrefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GPTMEMORYAPI_KEY_&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;memoryApiExample&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;handleMemoryApiRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;apiName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;contactEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;lastPolicyUpdate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;blobKeyPrefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;apiKeyPrefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Val imports the other Val that contains the whole memory API: &lt;a href="https://www.val.town/v/xkonti/gptMemoryManager"&gt;&lt;code&gt;xkonti/gptMemoryManager&lt;/code&gt;&lt;/a&gt;. It also defines a series of values that we can use to configure our API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;apiName&lt;/code&gt; - the name of your API. It will be used in the Privacy Policy (eg. &lt;code&gt;Memory API&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;contactEmail&lt;/code&gt; - the email to provide for contact in the Privacy Policy (eg. &lt;code&gt;some@email.com&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lastPolicyUpdate&lt;/code&gt; - the date the Privacy Policy was last updated (eg. &lt;code&gt;2023-11-28&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;blobKeyPrefix&lt;/code&gt; - the prefix for the blob storage keys used by your API - more info below (eg. &lt;code&gt;gpt:memories:&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;apiKeyPrefix&lt;/code&gt; - the prefix for you API Keys secrets - more info below (eg. &lt;code&gt;GPTMEMORYAPI_KEY_&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only the &lt;code&gt;contactEmail&lt;/code&gt; has to be changed. If you don't have a need, you can leave the rest as is.&lt;/p&gt;

&lt;p&gt;After making changes make sure to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hit the &lt;strong&gt;Save&lt;/strong&gt; button in the top right corner of the Val preview.&lt;/li&gt;
&lt;li&gt;Change the Val visibility to &lt;strong&gt;Unlisted&lt;/strong&gt; next to it's name - this will make it so that the API is accessible from anywhere, but it's not listed on your profile page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And just like that, you have your own API deployed on Val Town. On the bottom of your Val preview you should see a status bar with &lt;strong&gt;HTTP&lt;/strong&gt; and the address of your API. You can copy the address of your API from the status bar for later use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--I64xCgur--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f021bjo6ix0g2xl0bcc6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--I64xCgur--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f021bjo6ix0g2xl0bcc6.png" alt="The status bar displaying the URL of your API" width="800" height="52"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  API Key for your GPT
&lt;/h3&gt;

&lt;p&gt;The API we just deployed can be used by multiple GPTs, where each GPT has it's own separate memory (you can make them share as well if you wish so). Each GPT that will access the API needs to have a &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;API key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;name&lt;/code&gt; needs to be a unique alphanumeric name that will be used to identify that GPT. It will be used to relate the API key with the memory storage. Come up with a name for your GPT, for example &lt;code&gt;novelbuddy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;API key&lt;/code&gt; is a secret that will be used to authenticate the GPT with the API. It can be any alphanumeric string, but it's recommended to use a long random string. You can use a password generator to generate one. It could be something like &lt;code&gt;Wrangle-Chapped-Monkhood4-Domain-Suspend&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now that you have your &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;API key&lt;/code&gt; you need to add them as a secret to you Val account. You can navigate to the &lt;a href="https://www.val.town/settings/secrets"&gt;Secrets page&lt;/a&gt; and click the &lt;strong&gt;New secret&lt;/strong&gt; button. The &lt;code&gt;key&lt;/code&gt; of the secret will be the &lt;code&gt;apiKeyPrefix&lt;/code&gt; + &lt;code&gt;name&lt;/code&gt; of your GPT. For example, if your &lt;code&gt;apiKeyPrefix&lt;/code&gt; is &lt;code&gt;GPTMEMORYAPI_KEY_&lt;/code&gt; and your GPT &lt;code&gt;name&lt;/code&gt; is &lt;code&gt;novelbuddy&lt;/code&gt;, then the &lt;code&gt;key&lt;/code&gt; of the secret will be &lt;code&gt;GPTMEMORYAPI_KEY_novelbuddy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;value&lt;/code&gt; of the secret will be the &lt;code&gt;API key&lt;/code&gt; you generated for your GPT.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6y5S_3pn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n41teqqsfncp4nvabqvb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6y5S_3pn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n41teqqsfncp4nvabqvb.png" alt="The new secret to be saved" width="800" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hit &lt;strong&gt;Add&lt;/strong&gt; button to save the secret. Now we can move on to configuring your GPT.&lt;/p&gt;

&lt;h3&gt;
  
  
  GPT Action
&lt;/h3&gt;

&lt;p&gt;The API is now fully deployed and configured. Now we can focus on adding the long-term memory functionality to your GPT.&lt;/p&gt;

&lt;p&gt;If you don't have your custom GPT yet, create one. I used this prompt to generate one:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make a personal knowledge assistant that will assist me with learning processes, help make complex problems more approachable and summarize topics into something easier to remember.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Switch to the &lt;strong&gt;Configure&lt;/strong&gt; tab where we can customize our GPT precisely. On the bottom of the tab we can press the &lt;strong&gt;Create new action&lt;/strong&gt; button to create a new action for our GPT.&lt;/p&gt;

&lt;p&gt;The first thing we need is the OpenAPI schema for our API. We can get it from our API by adding &lt;code&gt;/openapi&lt;/code&gt; to the end of our API address. For example: &lt;code&gt;https://xkonti-memoryApiExample.web.val.run/openapi&lt;/code&gt;. Click the &lt;strong&gt;Import from URL&lt;/strong&gt; button and paste that address into the input field. Then hit the &lt;strong&gt;Import&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;Immediately you will see a red error message: &lt;code&gt;Could not find a valid URL in 'servers'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In the OpenAPI schema replace the &lt;code&gt;&amp;lt;APIURL&amp;gt;&lt;/code&gt; with the url of your API. Make sure you replace all capital letters with lowercase ones. For example: &lt;code&gt;"url": "https://xkonti-memoryapiexample.web.val.run"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now you should see that the schema was imported successfully as there are 5 available actions that loaded below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aMC6qg_Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9jeqx96gbp6lywe9tzci.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aMC6qg_Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9jeqx96gbp6lywe9tzci.png" alt="A list of imported actions" width="583" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's move on to the next step in the action configuration. The action needs to authenticate with the API, so let's set it in the &lt;strong&gt;Authentication&lt;/strong&gt; section. Click the &lt;strong&gt;None&lt;/strong&gt; and then select &lt;strong&gt;API Key&lt;/strong&gt; in the popup.&lt;/p&gt;

&lt;p&gt;The API Key needs to take shape of &lt;code&gt;&amp;lt;name&amp;gt;:&amp;lt;api-key&amp;gt;&lt;/code&gt;. In our example it will be: &lt;code&gt;novelbuddy:Wrangle-Chapped-Monkhood4-Domain-Suspend&lt;/code&gt;. But before pasting it into the &lt;strong&gt;API Key&lt;/strong&gt; field it needs to be base64 encoded first. You can use the &lt;a href="https://www.base64encode.org/"&gt;base64encode.org&lt;/a&gt; to do it quickly. Paste it into the input field and hit &lt;strong&gt;Encode&lt;/strong&gt;. Then copy the encoded string and paste it into the &lt;strong&gt;API Key&lt;/strong&gt; field. His &lt;strong&gt;Save&lt;/strong&gt; to save the authentication configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qdcMtOZk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dhypelym78wme3o9ypzj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qdcMtOZk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dhypelym78wme3o9ypzj.png" alt="Fully configured authentication popup" width="503" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last thing to configure is the &lt;strong&gt;Privacy Policy&lt;/strong&gt;. Simply paste your API url and add &lt;code&gt;/privacy&lt;/code&gt; at the end. For example: &lt;code&gt;https://xkonti-memoryApiExample.web.val.run/privacy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now we have the GPT action fully configured. You can press the &lt;code&gt;&amp;lt;&lt;/code&gt; chevron to get back to the GPT configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3gn66fZY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tvzc1ackfu16f8c4rk5p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3gn66fZY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tvzc1ackfu16f8c4rk5p.png" alt="Fully configured action" width="624" height="1024"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt
&lt;/h3&gt;

&lt;p&gt;Your GPT should have the long-term memory action at it's disposal now. Unfortunately it might not be sure how to use it properly yet. Let's add a prompt section that will instruct the GPT how to use it. Something like this works well in my case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Long-term memory&lt;/span&gt;

At some point the user might ask you to do something with "memory". Things like "remember", "save to memory", "forget", "update memory", etc. Please use corresponding actions to achieve those tasks. User might also ask you to perform some task with the context of your "memory" - in that case fetch all memories before proceeding with the task. The memories should be formed in a clear and purely informative language, void of unnecessary adjectives or decorative language forms. An exception to that rule might be a case when the language itself is the integral part of information (snippet of writing to remember for later, examples of some specific language forms, quotes, etc.).

Structure of a memory:
&lt;span class="p"&gt;
-&lt;/span&gt; name - a simple name of the memory to give it context at a glance
&lt;span class="p"&gt;-&lt;/span&gt; description - a detailed description of the thing that should be remembered. There is no length limit.
&lt;span class="p"&gt;-&lt;/span&gt; summary - a short summary of the memory. This should be formed in a way that will allow for ease of understanding which memories to retrieve in full detail just by reading the list of summaries. If there are some simple facts that have to be remembered and are the main point of the memory they should be included in the summary. The summary should also be written in a compressed way with all unnecessary words skipped if possible (more like a set of keywords or a Google Search input).
&lt;span class="p"&gt;-&lt;/span&gt; reason - the reason for the remembering - this should give extra information about the situation in which the memory was requested to be saved.

The memory accessed through those actions is a long-term memory persistent between various conversations with the user. You can assume that there already are many memories available for retrieval.

In some situations you might want to save some information to your memory for future recall. Do it in situations where you expect that some important details of the conversation might be lost and should be preserved.

Analogously you can retrieve memories at any point if the task at hand suggests the need or there isn't much information about the subject in your knowledge base.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hit the &lt;strong&gt;Save&lt;/strong&gt; button on the top right corner of the page to save the GPT. Thanks to the privacy policy being set in your action you can choose any publishing option.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚠️ Keep in mind that any user of your GPT will be able to read/add/remove the same set of memories. Nothing is private here.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;You should now have a fully functional GPT with a long-term memory. What can you do with it?&lt;/p&gt;

&lt;p&gt;The prompt we added to the GPT should allow you to use the &lt;code&gt;memory&lt;/code&gt; keyword in your prompts to hint the GPT that you want to use the long-term memory. You should be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ask for existing memories: &lt;code&gt;What do you have in your memory?&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ask it to search it's memory for something specific: &lt;code&gt;Do you remember the dimensions of my shed?&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ask it to remember something new: &lt;code&gt;Please remember that I need to buy some milk.&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ask it to remember something from the ongoing conversation: &lt;code&gt;Remember that. Make sure to note cost estimates we discussed.&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ask it to forget something: &lt;code&gt;Forget that I need to buy some milk.&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ask it to forget a memory you just talked about: &lt;code&gt;Delete this memory, please.&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ask it to forges a set of memories: &lt;code&gt;Forget all memories related taxes.&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Identify questions by it's id: &lt;code&gt;Please show me what "summary" you would generate for the memory "3a770850-849f-11ee-a182-4f0453a7b1ee" if you were saving it as a new memory.&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The long-term memory API is a surprisingly straight forward concept. It simply allows to manipulate a list of memories. There is nothing else to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tech stack
&lt;/h3&gt;

&lt;p&gt;The API is running on &lt;a href="https://www.val.town/"&gt;Val Town&lt;/a&gt; which uses &lt;a href="https://deno.com/"&gt;Deno runtime&lt;/a&gt;. The API is written in TypeScript and uses the &lt;a href="https://hono.dev/"&gt;Hono web framework&lt;/a&gt; to handle HTTP requests. The memories are stored using the &lt;a href="https://www.val.town/v/std/blob"&gt;Blob storage&lt;/a&gt; that Val Town provides.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storage
&lt;/h3&gt;

&lt;p&gt;The memories are sored in a &lt;a href="https://www.val.town/v/std/blob"&gt;Blob storage&lt;/a&gt; that Val Town provides. All memories related to a specific GPT (&lt;code&gt;name&lt;/code&gt;) are stored as a single JSON under the key &lt;code&gt;&amp;lt;blogKeyPrefix&amp;gt;&amp;lt;name&amp;gt;&lt;/code&gt;. In case of this example it would be &lt;code&gt;gpt:memories:novelbuddy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Each memory object contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt; - an unique identifier of the memory (UUID v4) - this is used to identify the memory.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt; - a simple name of the memory to give it context at a glance.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;description&lt;/code&gt; - a detailed description of the thing that should be remembered.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;summary&lt;/code&gt; - a short summary of the memory - this is used by ChatGPT to quickly search a list of memories.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reason&lt;/code&gt; - the reason for the remembering - this should give extra information about the situation in which the memory was requested to be saved.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As an example consider the following Val code that displays the memories stored from the demonstration at the beginning of this post:&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;blob&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;https://esm.town/v/std/blob&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showMemories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gpt:memories:echo&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It returns the following JSON stored in the blob:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3a770850-849f-11ee-a182-4f0453a7b1ee"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Immune Humans' Mutations"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"In the novel 'Abandoned Earth', a subset of humans develop genetic mutations that grant them immunity to The Entity's influence. This mutation seems to be a result of quantum fluctuations affecting biological processes, leading to unique genetic variations. These mutations confer not just resistance to The Entity's manipulation but also could possibly endow these humans with unique abilities or traits that differ from the general population. The nature, extent, and implications of these mutations are a significant aspect of the novel, impacting the plot and the interactions between characters, particularly in the context of the resistance against The Entity."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Subset of humans in 'Abandoned Earth' develop mutations granting immunity to The Entity's influence, possibly due to quantum fluctuations in biology. These mutations may give unique abilities and significantly impact plot and character dynamics, especially in resistance against The Entity."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"To further research and develop the concept of genetic mutations enabling immunity to The Entity's influence, particularly focusing on the implications of quantum fluctuations in biological processes."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a2a48eb0-8e2d-11ee-896d-175ca65d3a1a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Life Essence and Biological Processes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Explore the role of life essence in enabling biological processes, with a focus on how dimensional interference, particularly from the Incorporeal dimension, facilitates the existence of these processes. This exploration should delve into the complex interplay between the Physical and Incorporeal dimensions, examining how life essence acts as a crucial element in the emergence and sustenance of life in the Physical dimension."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Expanding on the role of life essence in biological processes, emphasizing the interaction between the Physical and Incorporeal dimensions."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"To deepen the understanding of how life essence, influenced by dimensional interference, is essential for biological processes in the novel's universe."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Endpoints
&lt;/h3&gt;

&lt;p&gt;The memories can be manipulated using 5 endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /memory&lt;/code&gt; - this endpoint returns a list of all memories. Those memories have the &lt;code&gt;summary&lt;/code&gt; field but lack &lt;code&gt;description&lt;/code&gt; field. This is to reduce the size of the returned JSON so that GPT can quickly search through the list of memories without confusing it too much with excessive details.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /memory/specific?ids=&amp;lt;id1,id2,id3...&amp;gt;&lt;/code&gt; - this endpoint returns a list of memories with the full details. It accepts a list of memory ids to retrieve. This is used when the GPT wants to retrieve a specific memory / memories. It's possible for the GPT to first retrieve the list of memories and then request the specific ones in a single prompt.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /memory&lt;/code&gt; - this endpoint allows to add a new memory. It accepts a JSON object with the memory details. It returns the &lt;code&gt;id&lt;/code&gt; of the newly created memory. The GPT can utilize the returned &lt;code&gt;id&lt;/code&gt; to reference the memory in the future.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PUT /memory/:id&lt;/code&gt; - this endpoint allows to update an existing memory. It needs the &lt;code&gt;id&lt;/code&gt; to be specified. If there is no memory with the specified &lt;code&gt;id&lt;/code&gt; it will return create na new memory instead.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DELETE /memory/:id&lt;/code&gt; - this endpoint allows to delete an existing memory. It needs the &lt;code&gt;id&lt;/code&gt; to be specified. If there is no memory with the specified &lt;code&gt;id&lt;/code&gt; it will return a 404 error.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally there are 2 more utility endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /privacy&lt;/code&gt; - this endpoint returns a simple privacy policy for the API.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /openapi&lt;/code&gt; - this endpoint returns the OpenAPI schema for the API. It currently isn't aware of the url it's deployed on, so it will return the schema with &lt;code&gt;&amp;lt;APIURL&amp;gt;&lt;/code&gt; placeholder. You need to replace it with the url of your API.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Privacy policy
&lt;/h3&gt;

&lt;p&gt;The privacy policy is a very simple policy generated by ChatGPT. It's generated by a separate Val:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.val.town/v/xkonti/memoryApiPolicy"&gt;https://www.val.town/v/xkonti/memoryApiPolicy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can easily fork it and point your API to that one instead.&lt;/p&gt;

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

&lt;p&gt;I hope you found this guide useful. This approach is very simplistic, yet it can be very powerful. There's a lot of room for improvement even by just changing up the prompt. Using IDs to reference memories from other memories? Providing an endpoint with more detailed description on how to use the long-term memory action to save some space in the prompt? Using a more complex storage to allow for more data to be stored and queried in a more interesting way? The possibilities are endless. Consider it a good starting point for your own experiments.&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>javascript</category>
      <category>api</category>
      <category>valtown</category>
    </item>
    <item>
      <title>Creating Quasar Framework project with Typescript support</title>
      <dc:creator>Xkonti</dc:creator>
      <pubDate>Mon, 16 Nov 2020 01:13:42 +0000</pubDate>
      <link>https://dev.to/xkonti/creating-quasar-framework-project-with-typescript-support-m9b</link>
      <guid>https://dev.to/xkonti/creating-quasar-framework-project-with-typescript-support-m9b</guid>
      <description>&lt;p&gt;&lt;a href="https://quasar.dev/"&gt;Quasar Framework&lt;/a&gt; advertises itself with: &lt;em&gt;"Build high-performance VueJS user interfaces in record time"&lt;/em&gt;. It is packed with features and developer-friendliness unlike any other Vue framework. If you've never heard of Quasar Framework, or unsure if you should give it a try, read the &lt;em&gt;&lt;a href="https://quasar.dev/introduction-to-quasar"&gt;Why quasar?&lt;/a&gt;&lt;/em&gt; documentation page.&lt;/p&gt;

&lt;p&gt;This guide will show you how to quickly create a Quasar Framework project with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TypeScript - to provide type safety and additional language features&lt;/li&gt;
&lt;li&gt;Class-based Vue components - to write Vue components in a more organized and TypeScript-friendly manner&lt;/li&gt;
&lt;li&gt;Class-based Vuex modules - to work with Vuex faster and safer - no more magic strings!&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;The code created by this tutorial is available as a project template on GitHub: &lt;a href="https://github.com/Xkonti/quasar-clean-typescript"&gt;xkonti/quasar-clean-typescript&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Project creation with Quasar CLI
&lt;/h2&gt;

&lt;p&gt;To start your journey with Quasar Framework install the &lt;a href="https://quasar.dev/start/quasar-cli"&gt;Quasar CLI&lt;/a&gt; globally by running the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @quasar/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn global add @quasar/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the installation is complete you'll need to create a directory for this project and then navigate to it. It's time to use &lt;em&gt;the power of Quasar CLI&lt;/em&gt; to save ourselves hours (or days) of tedious work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;quasar create
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will start a project wizard requesting some of the following information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Generate project in current directory?&lt;/strong&gt; - Definitely yes. You just navigated to this directory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project name&lt;/strong&gt; - This is the internal name of the project shown only to the developer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project product name&lt;/strong&gt; - This is the public name for the project, by default displayed as a title of the website.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project description&lt;/strong&gt; - A description of the project - it will be placed in the &lt;code&gt;package.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Author&lt;/strong&gt; - The name of the author.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick your favourite CSS preprocessor&lt;/strong&gt; - Choose whatever you prefer. For this template I'm selecting: &lt;code&gt;Sass with SCSS syntax&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick a Quasar components &amp;amp; directories import strategy&lt;/strong&gt; - The days of manually specifying used Quasar components are long gone and the &lt;em&gt;Auto-import&lt;/em&gt; option is a blessing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the features needed for your project&lt;/strong&gt; - This is an important aspect: deciding the features you want the Quasar CLI to set up for you automatically:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ESLint&lt;/code&gt; - For pre-configured linting.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TypeScript&lt;/code&gt; - For pre-configured TypeScript support. Required for this tutorial.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Vuex&lt;/code&gt; - For pre-configured Vuex integration. Required for this tutorial.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Axios&lt;/code&gt; - For pre-configured &lt;a href="https://github.com/axios/axios"&gt;Axios&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Vue-i18n&lt;/code&gt; - For pre-configured &lt;a href="https://kazupon.github.io/vue-i18n/"&gt;Vue-i18n&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick a component style&lt;/strong&gt; - Quasar lets you select a Vue component writing style. It'll preconfigure required plugins, and all example components will be appropriately adjusted. In this tutorial I'm sticking with &lt;code&gt;Class-based&lt;/code&gt; style as it is flexible, familiar and works well with Vue 2 and TypeScript. The &lt;code&gt;Composition API&lt;/code&gt; in &lt;code&gt;Vue 2&lt;/code&gt; has a great number of &lt;a href="https://github.com/vuejs/composition-api#limitations"&gt;limitations&lt;/a&gt;, so I'm not recommending it unless you are quite familiar with it. You can always go with the classic &lt;code&gt;Options API&lt;/code&gt;, but then you are losing some of the 🍭 that TypeScript brings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick an ESLint preset&lt;/strong&gt; - If you've selected the &lt;code&gt;ESLint&lt;/code&gt; to be preconfigured, Quasar CLI will ask you what preset to configure. For smaller projects I recommend the &lt;a href="https://standardjs.com/rules.html"&gt;&lt;code&gt;Standard&lt;/code&gt; preset&lt;/a&gt; as it's a fairly popular one, and it isn't strict to the point of annoyance. However, if you prefer something more strict because you work in a larger team, or you just prefer things this way, I recommend the &lt;a href="https://github.com/airbnb/javascript"&gt;&lt;code&gt;Airbnb&lt;/code&gt; preset&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continue to install project dependencies after the project has been created?&lt;/strong&gt; - Yes. Allow the Quasar CLI to install everything. The &lt;code&gt;Yarn&lt;/code&gt; package manager is recommended by the Quasar team, but the choice is yours.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Give the Quasar CLI some time to complete the installation, and you should finish with a project that's ready to run. Go to your project folder and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;quasar dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will start the development server for your newly created project. It supports automatic hot-reloading. It will even automatically reload when you change configuration files or install new packages unlike some other frameworks.&lt;/p&gt;

&lt;p&gt;Before continuing with the project setup, I recommend adding basic scripts in the &lt;code&gt;package.json&lt;/code&gt;. Just for convenience:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"quasar dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"quasar build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eslint --ext .js,.ts,.vue --ignore-path .gitignore ./"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;No test specified&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &amp;amp;&amp;amp; exit 0"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Quasar project overview
&lt;/h2&gt;

&lt;p&gt;A Quasar Framework project has a structure similar to most Vue.js projects. In the root directory there are &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;README.md&lt;/code&gt; and some other config files. Among which is the &lt;code&gt;quasar.conf.js&lt;/code&gt; - the main configuration file for Quasar. You will need to touch this file a couple of times throughout your project's life. &lt;a href="https://quasar.dev/quasar-cli/quasar-conf-js"&gt;Learn more&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;src&lt;/code&gt; directory has a typical structure with the &lt;code&gt;App.vue&lt;/code&gt; and &lt;code&gt;index.template.html&lt;/code&gt; - the Vue app component and HTML template for the whole app.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;boot&lt;/code&gt; directory contains code related to libraries that run before creation of the Vue instance. This is where the &lt;code&gt;Vue.use(...)&lt;/code&gt; &lt;em&gt;parts&lt;/em&gt; happen. &lt;a href="https://quasar.dev/quasar-cli/boot-files"&gt;Learn more&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;components&lt;/code&gt; directory is there to contain your Vue components, but it's completely optional. The &lt;code&gt;layouts&lt;/code&gt; directory contains application layout Vue components. Quasar has its own &lt;a href="https://quasar.dev/layout/layout"&gt;&lt;code&gt;QLayout&lt;/code&gt; component&lt;/a&gt; which allows you to quickly create familiar app layouts and supports pages (the &lt;a href="https://quasar.dev/layout/page"&gt;&lt;code&gt;QPage&lt;/code&gt; component&lt;/a&gt;), which reside in the &lt;code&gt;pages&lt;/code&gt; directory. Project generated by the Quasar CLI has a simple example of the &lt;code&gt;QLayout&lt;/code&gt; and the &lt;code&gt;QPage&lt;/code&gt; components relation as well as their configuration in the Vue router.&lt;/p&gt;

&lt;p&gt;There's also the &lt;code&gt;store&lt;/code&gt; directory which contains a Vuex store with an example of a Vuex module. We'll continue with this topic next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring class-based Vuex modules
&lt;/h2&gt;

&lt;p&gt;To be able to create Vuex store modules in a class-based manner we'll use the &lt;a href="https://championswimmer.in/vuex-module-decorators/"&gt;vuex-module-decorators package&lt;/a&gt;. It will allow you to use the Vuex store in a much easier way:&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;// Instead of using Vuex the old way:&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;$store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;layout/setLeftDrawer&lt;/span&gt;&lt;span class="dl"&gt;'&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="c1"&gt;// We'll be able to write this:&lt;/span&gt;
&lt;span class="nx"&gt;LayoutStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setLeftDrawer&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;All you need to do is install the package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;vuex-module-decorators
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add vuex-module-decorators
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Preparing the store
&lt;/h3&gt;

&lt;p&gt;First of all you can remove the &lt;code&gt;src/store/module-example&lt;/code&gt; folder with all files in it. This example shows a classic way of using Vuex with TypeScript.&lt;/p&gt;

&lt;p&gt;Since you probably won't be using the Vuex store in the standard way, you'll have to clean up the &lt;code&gt;src/store/index.ts&lt;/code&gt; file which is the main store instance.&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;store&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;quasar/wrappers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Vuex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Store&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;vuex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;storeInstance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;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;store&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;Vue&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Vuex&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;store&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;Store&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="na"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEBUGGING&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;storeInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to eliminating of the state interface, you'll also want to export the &lt;code&gt;storeInstance&lt;/code&gt; so you can use it when creating dynamic Vuex modules. Remember that the &lt;code&gt;storeInstance&lt;/code&gt; won't be available until Quasar creates it. Quasar Framework instantiates the store on its own using the &lt;code&gt;store&lt;/code&gt; wrapper function imported from &lt;code&gt;quasar/wrappers&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After having removed the state interface, you'll then have to fix the Vue router instantiation code located in &lt;code&gt;src/router/index.ts&lt;/code&gt;. Remove the &lt;code&gt;import { StateInterface } from '../store'&lt;/code&gt; import line and change the type of the store instance to match the &lt;code&gt;src/store/index.ts&lt;/code&gt;: &lt;code&gt;export default route&amp;lt;Store&amp;lt;unknown&amp;gt;&amp;gt;(function ({ Vue }) {&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a dynamic Vuex module 🔥
&lt;/h2&gt;

&lt;p&gt;Creating a dynamic class-based Vuex module is extremely easy. The following example illustrates the &lt;code&gt;LayoutStoreModule&lt;/code&gt; that handles the state of the left drawer in the &lt;code&gt;src/layouts/MainLayout.vue&lt;/code&gt; component. You can place the store module file wherever we'd like, but to keep things clean it's recommended to place it the &lt;code&gt;store&lt;/code&gt; folder. The module contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A state indicating whether the left drawer is open or not&lt;/li&gt;
&lt;li&gt;A mutation for setting the state&lt;/li&gt;
&lt;li&gt;An action for setting the state using the mutation
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;storeInstance&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;./index&lt;/span&gt;&lt;span class="dl"&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;Action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getModule&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;Mutation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;VuexModule&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;vuex-module-decorators&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;dynamic&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="na"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;storeInstance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;namespaced&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LayoutStoreModule&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;LayoutStoreModule&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;VuexModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;isLeftDrawerOpen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Mutation&lt;/span&gt;
  &lt;span class="nx"&gt;SET_LEFT_DRAWER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;setOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLeftDrawerOpen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;setOpen&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Action&lt;/span&gt;
  &lt;span class="nx"&gt;setLeftDrawer&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;setOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SET_LEFT_DRAWER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;setOpen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LayoutStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LayoutStoreModule&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Vuex store module takes the form of a class &lt;code&gt;LayoutStoreModule&lt;/code&gt; that extends the &lt;code&gt;VuexModule&lt;/code&gt; class. The state takes the form of the &lt;code&gt;isLeftDrawerOpen&lt;/code&gt; boolean field with a default value of &lt;code&gt;false&lt;/code&gt;. The &lt;code&gt;SET_LEFT_DRAWER&lt;/code&gt; function is marked as a store mutation using the &lt;code&gt;@Mutation&lt;/code&gt; decorator. The action for setting the state takes a form the &lt;code&gt;setLeftDrawer&lt;/code&gt; function decorated with the &lt;code&gt;@Action&lt;/code&gt; decorator.&lt;/p&gt;

&lt;p&gt;The whole module class is decorated with the &lt;code&gt;@Module&lt;/code&gt; decorator. The &lt;code&gt;@Module&lt;/code&gt; decorator accepts a configuration object. In the case of this module:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dynamic: true&lt;/code&gt; - this is a dynamic module. Dynamic modules are registered at runtime upon an &lt;code&gt;import&lt;/code&gt; statement.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;store: storeInstance&lt;/code&gt; - since this module will register itself, it then needs a reference to the store instance.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;namespaced: true&lt;/code&gt; - marks this module as a &lt;a href="https://vuex.vuejs.org/guide/modules.html#namespacing"&gt;namespaced module&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;name: 'LayoutStoreModule'&lt;/code&gt; - specifies module's name.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the end of the file, the module's instance is exported, so it can then be used directly in almost any place in the project:&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;LayoutStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setLeftDrawer&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using the dynamic vuex module
&lt;/h2&gt;

&lt;p&gt;Now you can use your newly-created store module. In the &lt;code&gt;MainLayout.vue&lt;/code&gt; component, you can replace the internal state with the one stored in the &lt;code&gt;LayoutStore&lt;/code&gt; module:&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="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;Vue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Component&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;vue-property-decorator&lt;/span&gt;&lt;span class="dl"&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;LayoutStore&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;src/store/LayoutStoreModule&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;EssentialLink&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;MainLayout&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Vue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;essentialLinks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;linksData&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;isLeftDrawerOpen&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;LayoutStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLeftDrawerOpen&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nx"&gt;isLeftDrawerOpen&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;LayoutStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setLeftDrawer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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;First import the newly-created &lt;code&gt;LayoutStore&lt;/code&gt; module. Then replace the previous &lt;code&gt;leftDrawerOpen&lt;/code&gt; field with the getter and setter &lt;code&gt;isLeftDrawerOpen&lt;/code&gt;. The getter simply returns the value from the store, while the setter dispatches the &lt;code&gt;setLeftDrawer&lt;/code&gt; action. The days of &lt;code&gt;dispatch&lt;/code&gt; and &lt;code&gt;commit&lt;/code&gt; alongside &lt;em&gt;magic strings&lt;/em&gt; are gone.&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="p"&gt;...&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;btn&lt;/span&gt;
  &lt;span class="nx"&gt;flat&lt;/span&gt;
  &lt;span class="nx"&gt;dense&lt;/span&gt;
  &lt;span class="nx"&gt;round&lt;/span&gt;
  &lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menu&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;aria&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Menu&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;click&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;isLeftDrawerOpen = !isLeftDrawerOpen&lt;/span&gt;&lt;span class="dl"&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;drawer&lt;/span&gt;
  &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;isLeftDrawerOpen&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;above&lt;/span&gt;
  &lt;span class="nx"&gt;bordered&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bg-grey-1&lt;/span&gt;&lt;span class="dl"&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/q-drawer&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, in the template section of the component, use the &lt;code&gt;isLeftDrawerOpen&lt;/code&gt; as a &lt;code&gt;v-model&lt;/code&gt; for the &lt;code&gt;q-drawer&lt;/code&gt; and in the &lt;code&gt;@click&lt;/code&gt; event in the &lt;code&gt;q-btn&lt;/code&gt;. That's it. Way simpler than classic Vuex and with a much better code autocompletion support. &lt;/p&gt;

&lt;h2&gt;
  
  
  Things to watch out for
&lt;/h2&gt;

&lt;p&gt;While these dynamic class-based modules bring an amazing coding experience, there are some limitations that you should be aware of. First of all, the module registers itself at its first import attempt. If the Vuex store hasn't instantiated yet, the module registration will fail.&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="p"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;routes&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;./routes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="c1"&gt;// Imports the module here - no store yet!&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;AuthStore&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;src/store/AuthStoreModule&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;default&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;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;Vue&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;VueRouter&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;Router&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;VueRouter&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;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The AuthStore module wasn't initialized properly.&lt;/span&gt;
    &lt;span class="c1"&gt;// The following statement fails.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AuthStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoggedIn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another thing worth noting, is that you can pass only one argument to the store module's actions and mutations - &lt;em&gt;payload&lt;/em&gt; - just like classic Vuex modules:&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;// Wrong! Can't pass 2 arguments&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Action&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;verifyUser&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;passwordHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&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="c1"&gt;// Pass 1 payload object instead&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Action&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;verifyUser&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="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;passwordHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nx"&gt;boolean&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;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Not only is it easy to configure the Quasar project to work well with TypeScript, but it's also fun to use class-based Vue components and class-based Vuex modules. I hope this will help those just starting out with Quasar and Typescript.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The code created by this tutorial is available as a project template on GitHub: &lt;a href="https://github.com/Xkonti/quasar-clean-typescript"&gt;xkonti/quasar-clean-typescript&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>quasar</category>
      <category>typescript</category>
      <category>vuex</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Starting with HubHazard</title>
      <dc:creator>Xkonti</dc:creator>
      <pubDate>Wed, 02 Sep 2020 03:50:02 +0000</pubDate>
      <link>https://dev.to/xkonti/starting-with-hubhazard-5ef2</link>
      <guid>https://dev.to/xkonti/starting-with-hubhazard-5ef2</guid>
      <description>&lt;p&gt;One of the main goals of &lt;a href="https://github.com/hubhazard/core"&gt;HubHazard&lt;/a&gt; is to make the setup step as easy as possible. In this post I'll present the simplest way of creating a HubHazard project - using the &lt;a href="https://github.com/hubhazard/hubhazard-basic-template"&gt;HubHazard Basic Template&lt;/a&gt;. In short:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new repository from the &lt;a href="https://github.com/hubhazard/hubhazard-basic-template"&gt;template&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Clone the repository&lt;/li&gt;
&lt;li&gt;Install dependencies (&lt;code&gt;npm install&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Start the server (&lt;code&gt;npm run start:dev&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Creating a Github repository from a template
&lt;/h2&gt;

&lt;p&gt;It's always a good idea to start your project by creating a git repository for it. Go to the &lt;a href="https://github.com/hubhazard/hubhazard-basic-template"&gt;HubHazard Basic Template github repository&lt;/a&gt; and press the &lt;code&gt;Use this template&lt;/code&gt; button to create a repository out of it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OBBGdnZ7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/tzekl0n3mffoo23f5lf6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OBBGdnZ7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/tzekl0n3mffoo23f5lf6.png" alt="`Use this template` button"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the next screen give your repository a name and click the &lt;code&gt;Create a repository from template&lt;/code&gt; button. You're new repo is ready to be cloned.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Z948M-tq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3zwc7oguf6wb9d8p3x02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Z948M-tq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3zwc7oguf6wb9d8p3x02.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After cloning the repo remember to install all packages by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Project startup and Nest.js basics
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/hubhazard/core"&gt;HubHazard&lt;/a&gt; is running on the &lt;a href="https://nestjs.com/"&gt;Nest.js&lt;/a&gt; framework:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nest is a framework for building efficient, scalable Node.js server-side applications. It uses modern JavaScript, is built with TypeScript (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;a href="https://nestjs.com/"&gt;Nest.js&lt;/a&gt; framework has an &lt;a href="https://docs.nestjs.com/"&gt;amazing documentation&lt;/a&gt;, but you don't need to know how it really works to use &lt;a href="https://github.com/hubhazard/core"&gt;HubHazard&lt;/a&gt;. It won't hurt to know something about &lt;a href="https://docs.nestjs.com/modules"&gt;modules&lt;/a&gt; and &lt;a href="https://docs.nestjs.com/providers"&gt;providers&lt;/a&gt; though.&lt;/p&gt;

&lt;p&gt;When working on your automations you can run the server in &lt;em&gt;development mode&lt;/em&gt;. To do that just run the &lt;code&gt;start:dev&lt;/code&gt; script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;num run start:dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project should compile and run. The project template comes with one example automation that shows a message every 15 seconds, so you should see those messages:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--svjw9i5J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ocujmzuetbdplrnoprf5.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--svjw9i5J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ocujmzuetbdplrnoprf5.gif" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens under the hood
&lt;/h2&gt;

&lt;p&gt;So what is really going on here under the hood? There are 4 main elements at play:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automations&lt;/strong&gt; - An automation is a piece of code executed when conditions described by its triggers are met. Each automation is represented by a class extending the &lt;a href="https://hubhazard.github.io/core/classes/automations.automation.html"&gt;Automation&lt;/a&gt; class. They register to the &lt;a href="https://hubhazard.github.io/core/classes/automations.automationsservice.html"&gt;AutomationsService&lt;/a&gt;.&lt;a href="https://hubhazard.github.io/core/classes/automations.automationsservice.html"&gt;automationsservice&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Events Services&lt;/strong&gt; - They act as events factories that send those events to subscribed automations. Each events service knows all of its subscribers. Each events service is represented by a class implementing the &lt;a href="https://hubhazard.github.io/core/interfaces/automations.ieventsservice.html"&gt;EventsService&lt;/a&gt; class. They register to the &lt;a href="https://hubhazard.github.io/core/classes/automations.automationsservice.html"&gt;AutomationsService&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automations Service&lt;/strong&gt; - A class managing registrations of automations and events services. It links them all together.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In case of the &lt;a href="https://github.com/hubhazard/hubhazard-basic-template"&gt;template project&lt;/a&gt; we have only one events service and only one automation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CAvKV2rW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/q25ofmas1rtfikxwo7ol.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CAvKV2rW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/q25ofmas1rtfikxwo7ol.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything starts with the &lt;code&gt;app.module.ts&lt;/code&gt; file. This is the main module of the server. All used events services and automations should be registered here.&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;AutomationsModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TimerEventsModule&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;@hubhazard/core&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;Module&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;@nestjs/common&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;ConfigModule&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;@nestjs/config&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;BasicAutomation&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;./automations/basic-automation&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="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;AutomationsModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;ConfigModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forRoot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;isGlobal&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="nx"&gt;TimerEventsModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;BasicAutomation&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;AppModule&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All events services mentioned in &lt;code&gt;imports&lt;/code&gt; will be automatically registered in the &lt;a href="https://hubhazard.github.io/core/classes/automations.automationsservice.html"&gt;AutomationsService&lt;/a&gt;. Similarly all automations mentioned in &lt;code&gt;providers&lt;/code&gt; will be automatically registered in the &lt;a href="https://hubhazard.github.io/core/classes/automations.automationsservice.html"&gt;AutomationsService&lt;/a&gt;. All you need to remember about when creating a new automation is to add it to the providers list in the &lt;code&gt;app.module.ts&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;When the &lt;code&gt;BasicAutomation&lt;/code&gt; is registered, it's passed to the &lt;a href="https://hubhazard.github.io/core/classes/timerevents.timereventsservice.html"&gt;TimerEventsService&lt;/a&gt;. The &lt;a href="https://hubhazard.github.io/core/classes/timerevents.timereventsservice.html"&gt;TimerEventsService&lt;/a&gt; scans triggers of the automation in search for ones it can handle. In case of &lt;code&gt;BasicAutomation&lt;/code&gt; there is one trigger definition asking to run the automation once every 15 seconds. The &lt;a href="https://hubhazard.github.io/core/classes/timerevents.timereventsservice.html"&gt;TimerEventsService&lt;/a&gt; remembers it and will send an &lt;a href="https://hubhazard.github.io/core/classes/automations.automationevent.html"&gt;automation event&lt;/a&gt; &lt;strong&gt;directly&lt;/strong&gt; to that automation every 15 seconds.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GWrFPJjK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bxbwnmh1ufw6wf0yzm5o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GWrFPJjK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bxbwnmh1ufw6wf0yzm5o.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All &lt;a href="https://hubhazard.github.io/core/classes/automations.automation.html"&gt;automations&lt;/a&gt; must implement the &lt;code&gt;handleEvent&lt;/code&gt; function. That function is called by events services and used to provide the &lt;a href="https://hubhazard.github.io/core/classes/automations.automationevent.html"&gt;automation events&lt;/a&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;Automation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TimerTrigger&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;@hubhazard/core&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;Injectable&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;@nestjs/common&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="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;BasicAutomation&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Automation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Basic automation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;triggers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;TimerTrigger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;every&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;seconds&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Handle the timer event&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;handleEvent&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;counter&lt;/span&gt;&lt;span class="o"&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="err"&gt;Triggered the '&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;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&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;counter&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt; times.&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Up next
&lt;/h2&gt;

&lt;p&gt;In the next article I'll explain how setup the&lt;a href="https://github.com/hubhazard/core"&gt;HubHazard&lt;/a&gt; server on &lt;a href="https://www.raspberrypi.org/products/raspberry-pi-zero-w/"&gt;Raspberry Pi Zero W&lt;/a&gt; - the $5 computer perfect for this purpose.&lt;/p&gt;

</description>
      <category>hubitat</category>
      <category>hubhazard</category>
      <category>automation</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Home automation with HubHazard</title>
      <dc:creator>Xkonti</dc:creator>
      <pubDate>Mon, 31 Aug 2020 05:54:57 +0000</pubDate>
      <link>https://dev.to/xkonti/home-automation-with-hubhazard-2gfe</link>
      <guid>https://dev.to/xkonti/home-automation-with-hubhazard-2gfe</guid>
      <description>&lt;p&gt;Most home automation systems are trying to take the user as far from the code as possible. You have to configure everything with buttons and drop-downs. When it comes to simple automations, like &lt;em&gt;&lt;code&gt;if-this-then-that&lt;/code&gt;&lt;/em&gt; it's super handy - a couple of clicks and you're done. This way of creating automations still works in some more complex situations, but when you have to involve variables and nested conditions, it becomes a nightmare. Modifying any of those &lt;em&gt;not-really-complex&lt;/em&gt; automations requires &lt;strong&gt;a lot of clicking&lt;/strong&gt; and quite often you can't even see the whole system at a glance since it exists in several independent pieces.&lt;/p&gt;

&lt;p&gt;As a software developer, I get pretty annoyed by the lack of an easy option of creating complex automations with code. I want my smart home to be &lt;strong&gt;really&lt;/strong&gt; smart while staying sane at the same time! That's where &lt;a href="https://github.com/hubhazard/core"&gt;HubHazard&lt;/a&gt; comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is HubHazard?
&lt;/h2&gt;

&lt;p&gt;Basically, &lt;a href="https://github.com/hubhazard/core"&gt;HubHazard&lt;/a&gt; is a simple Node.js server that allows you to easily write smart home automations using Typescript. It's targeted towards people with basic programming skills. You don't have to be a professional developer to use it effectively.&lt;/p&gt;

&lt;h3&gt;
  
  
  Easy to start with
&lt;/h3&gt;

&lt;p&gt;Just use the &lt;a href="https://github.com/hubhazard/hubhazard-basic-template"&gt;ready-made project template&lt;/a&gt;, add you configuration file (the template comes with an example file to copy) and you're ready to automate!&lt;/p&gt;

&lt;h3&gt;
  
  
  Easy to write automations
&lt;/h3&gt;

&lt;p&gt;Adding an automation to your smart home shouldn't be a hassle and that's why &lt;a href="https://github.com/hubhazard/core"&gt;HubHazard&lt;/a&gt; is allowing you to write an automation as a simple class:&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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;BasicAutomation&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Automation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Each automation needs to have a unique name&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Basic automation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Register trigger that will execute this automation every 15 seconds&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;triggers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;TimerTrigger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;every&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;seconds&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;

  &lt;span class="c1"&gt;// Handle the timer event&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;handleEvent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hey! I was triggered!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Easy local hosting
&lt;/h3&gt;

&lt;p&gt;Every smart home enthusiast wants their home to remain smart even when there's no internet connection. Most automation services rely on the cloud and when there's no connection, nothing works. &lt;a href="https://github.com/hubhazard/core"&gt;HubHazard&lt;/a&gt; solves this problem by being built on the Node.js platform. It's extremely easy to host on devices like the &lt;a href="https://dev.toraspberry"&gt;$5 Raspberry Pi Zero W&lt;/a&gt;. This way you can communicate with your hub directly. No more high latency and internet-dependent automations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Safety of Typescript
&lt;/h3&gt;

&lt;p&gt;The usage of Typescript in the &lt;a href="https://github.com/hubhazard/core"&gt;HubHazard&lt;/a&gt; instead of plain Javascript allows for a familiar coding experience (for JS devs) but with lots of safeguards protecting you from making mistakes. In addition, all modern IDEs will provide code suggestions, so there's less of a need to search for things in &lt;a href="https://github.com/hubhazard/core/wiki"&gt;the documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modular design
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/hubhazard/core"&gt;HubHazard&lt;/a&gt; server is built using the &lt;a href="https://nestjs.com/"&gt;Nest.js framework&lt;/a&gt;. This allows you to create your own modules that can extend the functionality of the server. If you want to integrate a different hub other than the Hubitat hub, go ahead and use the ready made &lt;a href="https://github.com/hubhazard/module-package-template"&gt;HubHazard module template&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does it exist?
&lt;/h2&gt;

&lt;p&gt;Throughout my journey of building a smart home, I used many different solutions to automate things. Each one of them had some problem, that in my case was considered a &lt;strong&gt;big problem&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Operations are performed in the cloud:

&lt;ul&gt;
&lt;li&gt;It introduces additional latency.&lt;/li&gt;
&lt;li&gt;No internet = nothing works.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;The lack of tools to express complex automations with ease.&lt;/li&gt;
&lt;li&gt;The process of creating an extensive automation is unpleasant.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All I wanted was a user-friendly way of writing automations using a popular programming language. I also wanted the automations to be executed locally. I ended up with the &lt;a href="https://hubitat.com/"&gt;Hubitat Elevation hub&lt;/a&gt; and created the &lt;a href="https://github.com/hubhazard/core"&gt;HubHazard&lt;/a&gt; server.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hubitat allows you to connect to Zigbee and Z-Wave devices as well as some cloud based devices via &lt;a href="https://ifttt.com/"&gt;IFTTT&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The HubHazard server connects to the Hubitat over WiFi and handles all automations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now everything is running locally, fast and exists in the form of a versionable Typescript codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Up next
&lt;/h2&gt;

&lt;p&gt;In the next article I'll explain how to start with &lt;a href="https://github.com/hubhazard/core"&gt;HubHazard&lt;/a&gt; in minutes using the &lt;a href="https://github.com/hubhazard/hubhazard-basic-template"&gt;HubHazard Basic Template&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>hubitat</category>
      <category>automation</category>
      <category>server</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Creating Quasar Framework project with Typescript support (pre v1.9.6)</title>
      <dc:creator>Xkonti</dc:creator>
      <pubDate>Fri, 18 Oct 2019 23:31:35 +0000</pubDate>
      <link>https://dev.to/xkonti/creating-quasar-framework-project-with-typescript-support-51ge</link>
      <guid>https://dev.to/xkonti/creating-quasar-framework-project-with-typescript-support-51ge</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Since release of &lt;a href="https://github.com/quasarframework/quasar/releases/tag/quasar-v1.9.6"&gt;Quasar 1.9.6&lt;/a&gt; and &lt;a href="https://github.com/quasarframework/quasar/releases/tag/%40quasar%2Fapp-v1.6.0"&gt;Quasar app 1.6&lt;/a&gt; this guide is inaccurate.&lt;/strong&gt; Read the &lt;a href="https://dev.to/xkonti/creating-quasar-framework-project-with-typescript-support-m9b"&gt;updated article&lt;/a&gt; if your using a recent version of Quasar.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://quasar.dev/"&gt;Quasar Framework&lt;/a&gt; is an amazing Vue.js framework providing not only extensive set of well-designed UI components, but also basic project structure, multiple build targets and plenty of other developer-friendly features.&lt;/p&gt;

&lt;p&gt;Today I want to show you how to setup a Quasar Framework project that has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Support for Typescript&lt;/li&gt;
&lt;li&gt;Support for class-based Vue components&lt;/li&gt;
&lt;li&gt;Support for class-based Vuex modules&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;The code created by this tutorial (with minor improvements) is available as a project template on GitHub: &lt;a href="https://github.com/Xkonti/quasar-clean-typescript/tree/pre-v-1.9.6"&gt;xkonti/quasar-clean-typescript on a branch &lt;code&gt;pre-v-1.9.6&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Quasar CLI
&lt;/h2&gt;

&lt;p&gt;To start your journey with Quasar Framework, it is recommended that you globally install the &lt;a href="https://quasar.dev/start/quasar-cli"&gt;Quasar CLI&lt;/a&gt; by running the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @quasar/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After it is installed, navigate to the directory in which you want to create the project. Now it's time to use the Quasar CLI to set things up for us:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;quasar create my-new-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This will start the new project wizard where you have to provide some information:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Project name&lt;/code&gt; - is simply your development project name. In this tutorial we'll leave it as it is suggested.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Project product name&lt;/code&gt; - is the official name of the application you're building. In this tutorial we'll input: &lt;code&gt;My New Project&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Project description&lt;/code&gt; - is simply a short description of your project. You can leave it empty. In this tutorial we'll input: &lt;code&gt;A brand new project.&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Author&lt;/code&gt; - just input your name or nickname or leave it empty&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Pick your favourite CSS preprocessor&lt;/code&gt; - this setting won't affect any future steps of this tutorial so choose what you're familiar with. In this tutorial I'll choose the most recommended option: &lt;code&gt;Sass with indented syntax&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Pick a Quasar components &amp;amp; directives import strategy&lt;/code&gt; - when using Vue components provided by the Quasar Framework, you'll need to import them first. Remember that you can always change this setting later in the &lt;code&gt;quasar.config.js&lt;/code&gt;. There are a couple of options to choose from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Auto-import in-use Quasar components &amp;amp; directives&lt;/code&gt; - this is a nice feature that lets the framework itself figure out what to import. Unfortunately, at the moment of writing this tutorial, &lt;strong&gt;this functionality doesn't work with Typescript&lt;/strong&gt;, so we can't use it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Manually specify what to import&lt;/code&gt; - this is the more tedious way of development, because it requires you to manually specify which components and directives you are using. You register them in the &lt;code&gt;quasar.config.js&lt;/code&gt; file. We'll go with this option in this tutorial because it makes the application as light as possible.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Import everything from Quasar&lt;/code&gt; - this imports absolutely everything there is in Quasar. It's convenient for development but the resulting build also includes everything, so the app is huge.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Check the features needed for your project&lt;/code&gt; - here we can select some additional features that are integrated into the Quasar Framework. In this tutorial, we'll select everything besides the &lt;code&gt;IE11 support&lt;/code&gt;. Remember that you select/deselect options using spacebar and go to the next step using the &lt;code&gt;enter&lt;/code&gt; key.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ESLint&lt;/code&gt; - a must have for a healthy code base&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Vuex&lt;/code&gt; - a Vuex store that we will use as the introduction to this tutorial suggests&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Axios&lt;/code&gt; - a promise based HTTP client&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Vue-i18n&lt;/code&gt; - a translations solution for Vue projects&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Pick an ESLint preset&lt;/code&gt; - pick an ESLint preset of your choice. I recommend the &lt;code&gt;Airbnb&lt;/code&gt; preset as it goes well with Typescript and enforces some good practices.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Cordova/Capacitor id&lt;/code&gt; - if you plan building the project as a mobile app, input the Cordova or Capacitor ID. If not, leave it default (as in this tutorial).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Should we run 'npm install' for you?&lt;/code&gt; - Choose whatever &lt;em&gt;"Yes"&lt;/em&gt; option you prefer.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After this the whole project should be automatically generated and all dependencies downloaded. You can go to the newly created project directory and run the application in development mode with the command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;quasar dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Quick project overview
&lt;/h2&gt;

&lt;p&gt;A Quasar project looks similar to most Vue.js projects. In the root folder there are &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;README.md&lt;/code&gt; and plenty of config files. The &lt;code&gt;quasar.conf.js&lt;/code&gt; is the main Quasar Framework config file where you specify used plugins, icon sets, components, directives, webpack options and various build options.&lt;/p&gt;

&lt;p&gt;Next to those files is the &lt;code&gt;src&lt;/code&gt; directory with quite a typical structure with the &lt;code&gt;App.vue&lt;/code&gt; and &lt;code&gt;index.template.html&lt;/code&gt; as a main component and base HTML template.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;boot&lt;/code&gt; directory contains some code related to plugins that runs before Vue instance start running. It's a perfect place to run some &lt;code&gt;Vue.use(...)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;components&lt;/code&gt; directory is intended to contain your Vue components, but it's completely optional. The &lt;code&gt;layouts&lt;/code&gt; directory is intended to contain application layout Vue components. Quasar has it's own &lt;code&gt;QLayout&lt;/code&gt; component which allows you to quickly create familiar app layouts and has support for pages (the &lt;code&gt;QPage&lt;/code&gt; component), which reside in the &lt;code&gt;pages&lt;/code&gt; directory. This generated project is a simple example of the &lt;code&gt;QLayout&lt;/code&gt; and the &lt;code&gt;QPage&lt;/code&gt; components relation as well as their configuration in the Vue router, which is situated in the &lt;code&gt;router&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;There is also the &lt;code&gt;store&lt;/code&gt; directory which contains a Vuex store with an example of a Vuex module.&lt;/p&gt;
&lt;h2&gt;
  
  
  Adding Typescript support
&lt;/h2&gt;

&lt;p&gt;Now it's time to finally add Typescript support to the project. The Quasar team created a handy extension to the Quasar Framework that automatically upgrades your project to Typescript. To install it just run the command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;quasar ext add @quasar/typescript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;During the installation process the script will ask you a couple of questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Please choose how to derive webpack&lt;/code&gt; - choose the recommended option&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Rename .js files to .ts&lt;/code&gt; - we are operating on a fresh Quasar project so it won't pose any problems - choose &lt;strong&gt;Y&lt;/strong&gt;es&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Will you use VSCode for this project?&lt;/code&gt; - it's up to you&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Generate Prettier configuration&lt;/code&gt; - if you're planning on using Prettier, then definitely &lt;strong&gt;Y&lt;/strong&gt;es&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Overwrite ".eslintrc.js"&lt;/code&gt; - if you're asked this question, choose &lt;code&gt;Owerwrite&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you'll try to run the project using the &lt;code&gt;quasar dev&lt;/code&gt; command it will run the app but with two errors:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR in C:/Dev/Tutorials/my-new-project/src/boot/i18n.ts(13,19):
TS7031: Binding element 'app' implicitly has an 'any' type.
ERROR in C:/Dev/Tutorials/my-new-project/src/store/index.ts(21,5):
TS2322: Type 'string | undefined' is not assignable to type 'boolean | undefined'.
  Type 'string' is not assignable to type 'boolean | undefined'.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Typescript isn't happy about those two files. After changing their extensions to &lt;code&gt;.ts&lt;/code&gt; they are checked more strictly. Let's quickly fix those errors.&lt;/p&gt;

&lt;p&gt;Open the &lt;code&gt;src/boot/i18n.ts&lt;/code&gt; file and add two lines above the default export:&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;// eslint-disable-next-line @typescript-eslint/ban-ts-ignore&lt;/span&gt;
&lt;span class="c1"&gt;// @ts-ignore&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Set i18n instance on app&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;i18n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i18n&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 &lt;code&gt;@ts-ignore&lt;/code&gt; comment forces the typescript linter to ignore the fact, that the &lt;code&gt;app&lt;/code&gt; argument is of type &lt;code&gt;any&lt;/code&gt; (which is prohibited). The &lt;code&gt;eslint-disable-next-line @typescript-eslint/ban-ts-ignore&lt;/code&gt; comment forces the typescript linter to ignore the rule to not allow usage of &lt;code&gt;@ts-ignore&lt;/code&gt;. It's not the most elegant solution, but it's enough in this case since you probably won't be needing to change it anyway.&lt;/p&gt;

&lt;p&gt;Now let's fix the problem with the store definition in the &lt;code&gt;src/store/index.ts&lt;/code&gt; file. Open it and simply make sure that the &lt;code&gt;strict&lt;/code&gt; property will get the &lt;code&gt;boolean&lt;/code&gt; value by comparing it to the &lt;code&gt;'true'&lt;/code&gt; string:&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="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* { ssrContext } */&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;Store&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;Vuex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// example&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// enable strict mode (adds overhead!)&lt;/span&gt;
    &lt;span class="c1"&gt;// for dev mode only&lt;/span&gt;
    &lt;span class="na"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Store&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, not very elegant, but it works. Now the project should run flawlessly.&lt;/p&gt;
&lt;h2&gt;
  
  
  Using class based Vue components
&lt;/h2&gt;

&lt;p&gt;Ok. We have Typescript support, but all Vue components are still written the usual Vue way. To add support for class-based Vue components install 2 additional packages:&lt;/p&gt;

&lt;p&gt;Install using npm:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-S&lt;/span&gt; vue-class-component
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-S&lt;/span&gt; vue-property-decorator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;or using Yarn:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add vue-class-component
yarn add vue-property-decorator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Those two packages allow us to fully use our new powers of Typescript decorators. Let's convert all components that exist in the project.&lt;/p&gt;
&lt;h4&gt;
  
  
  App.vue
&lt;/h4&gt;

&lt;p&gt;We need to change only the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; contents:&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;Vue&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;vue&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="nx"&gt;Component&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;vue-class-component&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="nd"&gt;Component&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Vue&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here we needed only to import the &lt;code&gt;Component&lt;/code&gt; decorator and change the component into class.&lt;br&gt;
The name of the component is now defined solely by the class name.&lt;/p&gt;
&lt;h4&gt;
  
  
  Error404.vue
&lt;/h4&gt;

&lt;p&gt;This is basically the same process as with the &lt;code&gt;App.vue&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="nx"&gt;Vue&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;vue&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="nx"&gt;Component&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;vue-class-component&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="nd"&gt;Component&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Error404&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Vue&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Index.vue
&lt;/h4&gt;

&lt;p&gt;Same as &lt;code&gt;App.vue&lt;/code&gt; and &lt;code&gt;Error404.vue&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="nx"&gt;Vue&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;vue&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="nx"&gt;Component&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;vue-class-component&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="nd"&gt;Component&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;PageIndex&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Vue&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  MyLayout view
&lt;/h4&gt;

&lt;p&gt;Here we have an example of a component with simple data. In a class-based Vue component, data is represented just by class field:&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;Vue&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;vue&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="nx"&gt;Component&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;vue-class-component&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="nd"&gt;Component&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;MyLayout&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Vue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;leftDrawerOpen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And that's all it took. If you want to learn more about class based Vue components, you can look into the two packages that we've just installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/vuejs/vue-class-component"&gt;vue-class-component on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kaorun343/vue-property-decorator"&gt;vue-property-decorator on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Using class based Vuex modules
&lt;/h2&gt;

&lt;p&gt;The next step to enjoy the full glory of Typescript is to use class-based Vuex modules. It not only makes them more readable, but also way easier to use, as Typescript will know all the data types. Let's install the npm package that we need:&lt;/p&gt;

&lt;p&gt;Install using npm:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; vuex-module-decorators
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;or using Yarn:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add &lt;span class="nt"&gt;-D&lt;/span&gt; vuex-module-decorators
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, delete the existing example Vuex module by removing whole &lt;code&gt;src/store/module-example&lt;/code&gt; directory and fix the store declaration in &lt;code&gt;src/store/index.ts&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="nx"&gt;Vue&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;vue&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="nx"&gt;Vuex&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;vuex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Vuex&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="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Vuex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&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;Instead of exporting the function that creates the Vuex store and importing modules there, we are creating a simple store instance here. This will easily let us create modules in any place we want.&lt;/p&gt;

&lt;p&gt;Let's create a &lt;code&gt;Layout&lt;/code&gt; module as an example of a class-based Vuex module. It will store the layout's &lt;code&gt;leftDrawerOpen&lt;/code&gt; state.&lt;/p&gt;

&lt;p&gt;Create a file &lt;code&gt;LayoutStoreModule.ts&lt;/code&gt; in the &lt;code&gt;src/layouts&lt;/code&gt; folder:&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;Action&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;Mutation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;VuexModule&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;vuex-module-decorators&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="nx"&gt;Store&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;../store/index&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="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;dynamic&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;layout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;namespaced&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="na"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Store&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="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;LayoutStoreModule&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;VuexModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;leftDrawerOpen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Mutation&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;SET_LEFT_DRAWER_OPEN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;leftDrawerOpen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&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="nd"&gt;Action&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;setLeftDrawerOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SET_LEFT_DRAWER_OPEN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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="nd"&gt;Action&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;toggleLeftDrawer&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;SET_LEFT_DRAWER_OPEN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&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;leftDrawerOpen&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;First we're importing all needed classes from the &lt;code&gt;vuex-module-decorators&lt;/code&gt; package and the store we created previously.&lt;/p&gt;

&lt;p&gt;Next is the declaration of the module along with the &lt;code&gt;@Module&lt;/code&gt; decorator that specifies some details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dynamic: true&lt;/code&gt; - this make it a dynamic module, which means that you don't have register it in the store itself&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;name: 'layout'&lt;/code&gt; - this specifies a namespace of this module, so internally all module's mutations, actions etc. will be prefixed with it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;namespaced: true&lt;/code&gt; - this marks a module as a namespaced module&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;store: Store&lt;/code&gt; - here we are linking the module to the store we created earlier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then we create a module state &lt;code&gt;leftDrawerOpen&lt;/code&gt; with a default value. It's simply a class field. Next we create a mutation that sets the &lt;code&gt;leftDrawerOpen&lt;/code&gt; state. Below it there are two actions that allows us to set and toggle the state.&lt;/p&gt;

&lt;p&gt;And that's it when it comes to store creation. Now let's use it in the &lt;code&gt;MyLayout.vue&lt;/code&gt; component. First we are going to modify the script part of the file:&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;Vue&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;vue&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="nx"&gt;Component&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;vue-class-component&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;getModule&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;vuex-module-decorators&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="nx"&gt;LayoutStoreModule&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;./LayoutStoreModule&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="nd"&gt;Component&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;MyLayout&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Vue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LayoutStoreModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;leftDrawerOpen&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;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;leftDrawerOpen&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nx"&gt;leftDrawerOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setLeftDrawerOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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;We started by importing the &lt;code&gt;getModule&lt;/code&gt; function that let's us get the dynamic module. Then we also imported the module class itself.&lt;/p&gt;

&lt;p&gt;Inside the component we got rid of the &lt;code&gt;leftDrawerOpen&lt;/code&gt; field and created the &lt;code&gt;store&lt;/code&gt; field instead that will hold the Vuex module that we got from &lt;code&gt;getModule&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then we declared a computed property (&lt;code&gt;get leftDrawerOpen()&lt;/code&gt;) that gets the store's state. We also created the setter (&lt;code&gt;set leftDrawerOpen(value: boolean)&lt;/code&gt;) for this computed property so that it works well with the &lt;code&gt;QDrawer&lt;/code&gt; component that might try to change the &lt;code&gt;v-model&lt;/code&gt; if someone clicks outside of the opened drawer. Here we used the module's action to set the state.&lt;/p&gt;

&lt;p&gt;In the template, we don't have to touch the &lt;code&gt;v-model&lt;/code&gt; of the &lt;code&gt;QDrawer&lt;/code&gt; component, as it's still the same name. We don't even have to change the &lt;code&gt;@click&lt;/code&gt; event of the &lt;code&gt;QBtn&lt;/code&gt; that opens/closes the drawer. But if we want to, we can use the &lt;code&gt;toggleLeftDrawer&lt;/code&gt; action here in the template:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;q-btn&lt;/span&gt;
  &lt;span class="na"&gt;flat&lt;/span&gt;
  &lt;span class="na"&gt;dense&lt;/span&gt;
  &lt;span class="na"&gt;round&lt;/span&gt;
  &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"store.toggleLeftDrawer()"&lt;/span&gt;
  &lt;span class="na"&gt;icon=&lt;/span&gt;&lt;span class="s"&gt;"menu"&lt;/span&gt;
  &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Menu"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you want to learn more about class-based Vuex modules, take a look at the package we've just installed: &lt;a href="https://championswimmer.in/vuex-module-decorators/pages/overview.html"&gt;vuex-module-decorators website&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;It's not only easy to configure the Quasar project to work well with TypeScript, but it's also fun to use class-based Vue components and class-based Vuex modules. I hope this will help some who are starting out with Quasar and Typescript.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The code created by this tutorial (with minor improvements) is available as a project template on GitHub: &lt;a href="https://github.com/Xkonti/quasar-clean-typescript/tree/pre-v-1.9.6"&gt;xkonti/quasar-clean-typescript on a branch &lt;code&gt;pre-v-1.9.6&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vJ70wriM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Xkonti"&gt;
        Xkonti
      &lt;/a&gt; / &lt;a href="https://github.com/Xkonti/quasar-clean-typescript"&gt;
        quasar-clean-typescript
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Simple Vue.js starter project using Quasar Framework, official typescript extension and couple additional packages for class based components and class based Vuex store modules.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Quasar - Clean Typescript Template&lt;/h1&gt;
&lt;p&gt;A simple Vue.js starter project using &lt;a href="https://quasar.dev/" rel="nofollow"&gt;Quasar Framework&lt;/a&gt; with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Axios&lt;/li&gt;
&lt;li&gt;ESLint with Standard preset&lt;/li&gt;
&lt;li&gt;Sass with SCSS syntax&lt;/li&gt;
&lt;li&gt;TypeScript with class-based components&lt;/li&gt;
&lt;li&gt;Vue-i18n&lt;/li&gt;
&lt;li&gt;Vuex with class-based modules&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
Do it yourself&lt;/h2&gt;
&lt;p&gt;If you would like to recreate this project on your own or customize something, you can follow the &lt;a href="https://xkonti.tech/blog/quasar-framework-with-typescript/" rel="nofollow"&gt;tutorial explaining how this template was created&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
Installation&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Clone this repository.&lt;/li&gt;
&lt;li&gt;Rename project name, description, author etc. in &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;quasar.conf.js&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;yarn install&lt;/code&gt; or &lt;code&gt;npm install&lt;/code&gt; command to install dependencies.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;dev&lt;/code&gt; script from &lt;code&gt;package.json&lt;/code&gt; to run the dev server.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
Previous versions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Pre Quasar v1.9.6 version of this template was moved to the &lt;a href="https://github.com/Xkonti/quasar-clean-typescript/tree/pre-v-1.9.6"&gt;&lt;code&gt;pre-v-1.9.6&lt;/code&gt; branch&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Xkonti/quasar-clean-typescript"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



</description>
      <category>vue</category>
      <category>quasar</category>
      <category>typescript</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
