<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Chris Cook</title>
    <description>The latest articles on Forem by Chris Cook (@zirkelc).</description>
    <link>https://forem.com/zirkelc</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%2F632215%2F83b13af4-ca36-4ceb-8c38-40737fe8087a.jpg</url>
      <title>Forem: Chris Cook</title>
      <link>https://forem.com/zirkelc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/zirkelc"/>
    <language>en</language>
    <item>
      <title>AI SDK Streaming Text from Lambda</title>
      <dc:creator>Chris Cook</dc:creator>
      <pubDate>Wed, 08 Oct 2025 12:13:53 +0000</pubDate>
      <link>https://forem.com/aws-builders/ai-sdk-streaming-text-from-lambda-cfd</link>
      <guid>https://forem.com/aws-builders/ai-sdk-streaming-text-from-lambda-cfd</guid>
      <description>&lt;p&gt;The &lt;a href="https://ai-sdk.dev/docs/foundations/streaming" rel="noopener noreferrer"&gt;AI SDK&lt;/a&gt; has two primary modes to interact with a language model: generation and streaming.&lt;/p&gt;

&lt;p&gt;Generation means the model returns a complete message. That can be a word, a sentence, paragraph, or complete article.&lt;br&gt;
In streaming mode, the model starts to return multiple partial messages - chunks - while it is generating a response. This is how we usually interact with ChatGPT and other chatbots.&lt;/p&gt;

&lt;p&gt;The AI SDK docs already provide lots of examples. While its primary focus might be the usage via Next.js deployed on Vercel, it is built in a vendor-agnostic way so it should run on any Node.js platform. &lt;/p&gt;

&lt;p&gt;In this post, I'll port the official example &lt;a href="https://ai-sdk.dev/cookbook/api-servers/node-http-server#text-stream" rel="noopener noreferrer"&gt;Node.js HTTP text streaming&lt;/a&gt; to work on Lambda.&lt;/p&gt;
&lt;h2&gt;
  
  
  Lambda Response Streaming
&lt;/h2&gt;

&lt;p&gt;AWS introduced response streaming for Lambda functions a while ago, but it looks like it didn't really get into mainstream yet. A reason could be that it is still hard to find examples for using it correctly. Every example I found seems to derive from the official announcement &lt;a href="https://aws.amazon.com/blogs/compute/introducing-aws-lambda-response-streaming/" rel="noopener noreferrer"&gt;Introducing AWS Lambda response streaming&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;awslambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;streamifyResponse&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;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setContentType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/plain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, world!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&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 biggest pain point is the missing type-safety. A few months ago I went through all available docs and examples and extended the official &lt;a href="https://www.npmjs.com/package/@types/aws-lambda" rel="noopener noreferrer"&gt;&lt;code&gt;@types/aws-lambda&lt;/code&gt;&lt;/a&gt; package with types for streaming:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/DefinitelyTyped/DefinitelyTyped/pull/72417" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        feat(aws-lambda): add response streaming types
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#72417&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/zirkelc" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F950244%3Fv%3D4" alt="zirkelc avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/zirkelc" rel="noopener noreferrer"&gt;zirkelc&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/DefinitelyTyped/DefinitelyTyped/pull/72417" rel="noopener noreferrer"&gt;&lt;time&gt;Apr 07, 2025&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;Please fill in this template.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[X] Use a meaningful title for the pull request. Include the name of the package modified.&lt;/li&gt;
&lt;li&gt;[X] Test the change in your own code. (Compile and run.)&lt;/li&gt;
&lt;li&gt;[X] &lt;a href="https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/README.md#my-package-teststs" rel="noopener noreferrer"&gt;Add or edit tests&lt;/a&gt; to reflect the change.&lt;/li&gt;
&lt;li&gt;[X] Follow the advice from the &lt;a href="https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/README.md#make-a-pull-request" rel="noopener noreferrer"&gt;readme&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;[X] Avoid &lt;a href="https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/README.md#common-mistakes" rel="noopener noreferrer"&gt;common mistakes&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;[X] &lt;a href="https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/README.md#running-tests" rel="noopener noreferrer"&gt;Run &lt;code&gt;pnpm test aws-lambda&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Select one of these and delete the others:&lt;/p&gt;
&lt;p&gt;If changing an existing definition:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[x] Provide a URL to documentation or source code which provides context for the suggested changes: &lt;a href="https://aws.amazon.com/blogs/compute/introducing-aws-lambda-response-streaming/" rel="nofollow noopener noreferrer"&gt;https://aws.amazon.com/blogs/compute/introducing-aws-lambda-response-streaming/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/DefinitelyTyped/DefinitelyTyped/pull/72417" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;





&lt;p&gt;Installing the package will make the global &lt;code&gt;awslambda&lt;/code&gt; namespace available, with the &lt;code&gt;streamifyResponse()&lt;/code&gt; function and the &lt;code&gt;HttpResponseStream&lt;/code&gt; 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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StreamifyHandler&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;aws-lambda&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;streamifyResponseHandler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StreamifyHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&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;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CustomHeader&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;outerspace&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="nx"&gt;responseStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;awslambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HttpResponseStream&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;responseStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setContentType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/plain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, world!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&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;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;awslambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;streamifyResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;streamifyResponseHandler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  AI SDK Text Streaming
&lt;/h2&gt;

&lt;p&gt;The official AI SDK example for Node.js uses the &lt;a href="https://github.com/vercel/ai/blob/11eefa4fdfddeae11e94224ec820afa67891e78e/packages/ai/src/text-stream/pipe-text-stream-to-response.ts" rel="noopener noreferrer"&gt;&lt;code&gt;pipeTextStreamToResponse&lt;/code&gt;&lt;/a&gt; function to stream the text back to the client:&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;openai&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;@ai-sdk/openai&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;streamText&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;ai&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;createServer&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;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;createServer&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;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;streamText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-4o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invent a new holiday and describe its traditions.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipeTextStreamToResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looking at the implementation of &lt;a href="https://github.com/vercel/ai/blob/11eefa4fdfddeae11e94224ec820afa67891e78e/packages/ai/src/text-stream/pipe-text-stream-to-response.ts" rel="noopener noreferrer"&gt;&lt;code&gt;pipeTextStreamToResponse&lt;/code&gt;&lt;/a&gt;, it reads the &lt;code&gt;textStream&lt;/code&gt; from the &lt;code&gt;streamText()&lt;/code&gt; result and writes it to the &lt;code&gt;ServerResponse&lt;/code&gt; from &lt;code&gt;node:http&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, this does not work on Lambda because we cannot return the &lt;code&gt;ServerResponse&lt;/code&gt; object; instead, we must write to the output stream &lt;code&gt;responseStream&lt;/code&gt;. Since we're dealing with streams, we can use Node.js &lt;code&gt;pipeline()&lt;/code&gt; to simply pipe &lt;code&gt;result.textStream&lt;/code&gt; to the Lambda response stream.&lt;/p&gt;

&lt;p&gt;Here is the Node.js example from above, converted to work on Lambda:&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;bedrock&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;@ai-sdk/amazon-bedrock&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;streamText&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;ai&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;pipeline&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;node:stream/promises&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyEventV2&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;aws-lambda&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;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;awslambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;streamifyResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;APIGatewayProxyEventV2&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;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setContentType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/plain; charset=utf-8&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;streamText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us.anthropic.claude-sonnet-4-5-20250929-v1: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;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invent a new holiday and describe its traditions.&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;await&lt;/span&gt; &lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;responseStream&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;It's important to set the right content type &lt;code&gt;text/plain&lt;/code&gt; via &lt;code&gt;responseStream.setContentType()&lt;/code&gt; at the beginning, otherwise your browser or client won't render the incoming text chunks correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lambda Function URL
&lt;/h2&gt;

&lt;p&gt;Lambda response streaming works only with Function URLs, not with the API Gateway. When you deploy your Lambda function, you must set the &lt;code&gt;InvokeMode&lt;/code&gt; to &lt;code&gt;RESPONSE_STREAM&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;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AISDKChatStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&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;streamFunction&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;NodejsFunction&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aisdk-stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aisdk-stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/functions/stream.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_22_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;memorySize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&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;streamFunctionUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;streamFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addFunctionUrl&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;authType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FunctionUrlAuthType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;invokeMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InvokeMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RESPONSE_STREAM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;allowedOrigins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ALL_ORIGINS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing in the Browser
&lt;/h2&gt;

&lt;p&gt;When deployed, you can call the Lambda Function URL in the browser and you'll see the output appearing chunk by chunk:&lt;/p&gt;

&lt;p&gt;

&lt;iframe src="https://player.vimeo.com/video/1125497766" width="710" height="399"&gt;
&lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;Here's the complete code including the CDK stack to deploy to AWS: &lt;a href="https://github.com/zirkelc/ai-sdk-lambda-streaming" rel="noopener noreferrer"&gt;github.com/zirkelc/ai-sdk-lambda-streaming&lt;/a&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>typescript</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Lambdalet.AI: Reinventing the bookmarklet</title>
      <dc:creator>Chris Cook</dc:creator>
      <pubDate>Thu, 10 Jul 2025 10:15:04 +0000</pubDate>
      <link>https://forem.com/aws-builders/lambdaletai-reinventing-the-bookmarklet-3772</link>
      <guid>https://forem.com/aws-builders/lambdaletai-reinventing-the-bookmarklet-3772</guid>
      <description>&lt;p&gt;I would like to present Lambdalet.AI, my submission for the &lt;a href="https://devpost.com/software/lambdalet-ai?ref_content=user-portfolio&amp;amp;ref_feature=in_progress" rel="noopener noreferrer"&gt;AWS Lambda Hackathon&lt;/a&gt;. &lt;/p&gt;




&lt;p&gt;Lambdalet.AI (&lt;em&gt;Lambda&lt;/em&gt; + &lt;del&gt;bookmark&lt;/del&gt;&lt;em&gt;let&lt;/em&gt;) is an AI-powered bookmarking and read-it-later service. It uses a dynamic JavaScript bookmark — a so-called &lt;a href="https://en.wikipedia.org/wiki/Bookmarklet" rel="noopener noreferrer"&gt;bookmarklet&lt;/a&gt; — to send the current page's HTML to an AWS Lambda function. The Lambda function invokes a Large Language Model on Bedrock to extract the page's main content — ignoring headers, footers, and other irrelevant elements — and saves it to a Notion database. The Notion database stores all our bookmarks and allows us to find bookmarks by title, URL, and even their content.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/1099945443" width="710" height="399"&gt;
&lt;/iframe&gt;Saving a full page to Notion
&lt;/p&gt;
&lt;h2&gt;
  
  
  What’s a Bookmarklet?
&lt;/h2&gt;

&lt;p&gt;A bookmarklet is a snippet of JavaScript stored in a bookmark; it runs in the context of the page you are viewing by using the &lt;code&gt;javascript:&lt;/code&gt; URL scheme. For instance, this bookmarklet makes any website editable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;javascript&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentEditable&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;designMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;on&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="mi"&gt;0&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;Add it as a bookmark, click it on any site (even on this article), and you can freely edit the page.&lt;/p&gt;

&lt;p&gt;Bookmarklets were once hugely popular because services such as Instapaper, Evernote, and Pocket could extend browsers without requiring a full extension. The rise of Content Security Policy (CSP) has broken many bookmarklets, even though the spec explicitly states CSP &lt;a href="https://www.w3.org/TR/CSP/#extensions" rel="noopener noreferrer"&gt;shouldn’t interfere&lt;/a&gt; with them. In practice, behavior varies by browser and site, but many pages still allow bookmarklets — or we can often work around these limitations, as we’ll see later.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Save Bookmarks to Notion?
&lt;/h2&gt;

&lt;p&gt;Notion is my command center for notes, tasks, and even my blog &lt;a href="https://github.com/zirkelc/zirkelc.dev" rel="noopener noreferrer"&gt;zirkelc.dev&lt;/a&gt; (be sure to give it a star!). Notion databases excel at structured data, and their search is world-class. It always bothered me that browsers save only a title and URL, which often leaves me hunting for an article I &lt;em&gt;know&lt;/em&gt; I read but just can’t find again. Capturing the full content solves that.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;You can try Lambdalet.AI right now — no deployment required:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Open the Notion database&lt;/strong&gt;: Open my shared &lt;a href="https://zirkelc.notion.site/20c00d5ef00e802a8cd1de77eafebc4f?v=20c00d5ef00e80c8adb5000cca955976" rel="noopener noreferrer"&gt;Notion database&lt;/a&gt; in a new tab. New pages will appear here automatically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create the bookmarklet&lt;/strong&gt;: Add a new bookmark and paste &lt;a href="https://github.com/zirkelc/lambdalet/blob/master/bookmarklets/bookmarklet.js" rel="noopener noreferrer"&gt;this code&lt;/a&gt; below as its URL.&lt;/p&gt;

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


&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pick any website&lt;/strong&gt;: Like for example, this article. Requests are deduplicated by URL, so multiple clicks on the same page simply update the entry.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that if you select any text on the website, only this selection will be saved to Notion&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Click the bookmarklet&lt;/strong&gt;: If the site’s CSP blocks &lt;code&gt;fetch&lt;/code&gt;, the code falls back to a form submission in a temporary window.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Check Notion&lt;/strong&gt;: A new entry appears. Content extraction is asynchronous, so text may take a moment to appear.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;Let’s follow the request as it travels from your browser to Notion. For more depth, see the repo’s README and the code.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdkrqjdnab4bezd7js7qq.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdkrqjdnab4bezd7js7qq.jpeg" alt="Architecture"&gt;&lt;/a&gt;&lt;/p&gt;
Solution architecture with request flow



&lt;p&gt;On any website you want to save, you can click the bookmarklet in your browser. It will send the current page's HTML, title and URL in a &lt;code&gt;POST&lt;/code&gt; request to a REST API Gateway. The bookmarklet is designed to first try a simple &lt;code&gt;fetch&lt;/code&gt; call. As this is often blocked by a restrictive Content Security Policy (CSP), the bookmarklet falls back on a form submission which temporarily opens a new window (to avoid navigating way from the current page).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why POST instead of GET?&lt;/strong&gt;&lt;br&gt;
A POST request lets the client send the fully rendered HTML — no server-side scraping required. That avoids running a headless browser in Lambda and sidesteps localization issues that would arise if the Lambda IP came from a different region than the user.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The REST API is protected by an API key with usage quotas. Because form submissions can’t set custom headers such as &lt;code&gt;x-api-key&lt;/code&gt;, the key is passed as a query-string parameter and verified by a custom Lambda authorizer.  &lt;/p&gt;

&lt;p&gt;Once past the API Gateway, the request reaches the first Lambda function, whose only job is deduplication. Rather than processing every click immediately, the function writes a message to an SQS FIFO queue keyed by URL. Modern pages easily exceed the 256 KB SQS payload limit, so the raw HTML is uploaded to S3 and the queue message contains only the S3 URI. Multiple clicks on the same URL therefore collapse into a single queued job.  &lt;/p&gt;

&lt;p&gt;A second Lambda polls the queue. Each message is handled in its own invocation to avoid timeouts while it waits for Bedrock. The function downloads the HTML from S3, then checks whether a Notion page for that URL already exists. If it does, the old page is archived and a  new empty page is created. Archiving is cheaper than deleting the page's content block-by-block; the Notion API’s rate limits make large-scale deletes expensive.  &lt;/p&gt;

&lt;p&gt;The new Notion page initially contains only the title and URL. The raw HTML is first converted to Markdown to reduce token count, then passed to Amazon Bedrock (Claude 3.7 Sonnet) to extract only the main content — no headers, nav bars, cookie banners, or ads. &lt;/p&gt;

&lt;p&gt;
  Prompt
  &lt;br&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;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
Here is the content from the URL converted from HTML to markdown:
&amp;lt;url&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/url&amp;gt;

&amp;lt;markdown&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
&amp;lt;/markdown&amp;gt;

Your task is to extract the main content from the given markdown.

Wrap your response in &amp;lt;content&amp;gt; tags.
&amp;lt;content&amp;gt;
[Your markdown content here]
&amp;lt;/content&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;The model's Markdown output is converted into Notion block objects and appended to the page. Finally, the status of the Notion page is updated to indicate the completion of processing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All Claude models support 200K (~150K words) token context windows, but the practical ceiling is actually the output limit. Only the latest generations, Claude Sonnet 3.7 and 4.0, have raised the limit from 8K to 64K tokens (~48 K words), making such use cases possible.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And that’s it: a click of the bookmarklet becomes a clean, searchable page in Notion.&lt;/p&gt;
&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;I built earlier prototypes in the pre-LLM era, but only recent models, with much larger contexts and outputs, make this practical. Here are a few things I'd like to implement in future iterations.&lt;/p&gt;
&lt;h3&gt;
  
  
  Richer Metadata
&lt;/h3&gt;

&lt;p&gt;Great bookmarks must be easy to rediscover. Beyond full text, I want  tags, cover images, and auto-generated summaries.&lt;/p&gt;
&lt;h3&gt;
  
  
  Better CSP Handling
&lt;/h3&gt;

&lt;p&gt;The current fallback uses form submission, but some sites — I'm looking at you, GitHub — block even that. I'm exploring additional fallback mechanisms like &lt;code&gt;window.open&lt;/code&gt; to open a new tab, though that forces a &lt;code&gt;GET&lt;/code&gt; request, so we’d need to fetch HTML server-side — likely with a headless browser.&lt;/p&gt;
&lt;h3&gt;
  
  
  Refactor with Step Functions
&lt;/h3&gt;

&lt;p&gt;Today the second Lambda idles while waiting for the Bedrock response. Converting the flow to Step Functions would let us use the &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/connect-bedrock.html#connect-bedrock-custom-apis" rel="noopener noreferrer"&gt;native Bedrock integration&lt;/a&gt;, reducing cost and avoiding timeouts. &lt;/p&gt;
&lt;h2&gt;
  
  
  Deploy Your Own
&lt;/h2&gt;

&lt;p&gt;Setting up your own Lambdalet.AI instance is easy — full setup instructions are in the repo. Be sure to give it a star and happy bookmarking! 🔖 📑&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/zirkelc" rel="noopener noreferrer"&gt;
        zirkelc
      &lt;/a&gt; / &lt;a href="https://github.com/zirkelc/lambdalet" rel="noopener noreferrer"&gt;
        lambdalet
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Lambdalet.AI: Reinventing the bookmarklet.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Lambdalet.AI&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/zirkelc/lambdalet/blob/2b5db5984ecd55c90de6953b973122406a5d2e6f/images/banner.jpeg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fzirkelc%2Flambdalet%2Fraw%2F2b5db5984ecd55c90de6953b973122406a5d2e6f%2Fimages%2Fbanner.jpeg" alt="Lambdalet.AI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lambdalet.AI (&lt;em&gt;Lambda&lt;/em&gt; + &lt;del&gt;bookmark&lt;/del&gt;&lt;em&gt;let&lt;/em&gt;) is an AI-powered bookmarking and read-it-later service. It uses a dynamic javascript bookmark - a so called &lt;a href="https://en.wikipedia.org/wiki/Bookmarklet" rel="nofollow noopener noreferrer"&gt;bookmarklet&lt;/a&gt; - to send the current page's HTML to an AWS Lambda function. The Lambda function invokes a Large Language Model on Bedrock to extract the page's main content - ignoring headers, footers and other non-content elements - and saves it to a Notion database. The Notion database stores all our bookmarks and allows us to find bookmarks by title, URL and even their content.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Try It&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;You can try Lambdalet.AI without deploying anything. Follow these steps:&lt;/p&gt;


&lt;ol&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Open Notion Database&lt;/strong&gt;: Open the shared &lt;a href="https://zirkelc.notion.site/20c00d5ef00e802a8cd1de77eafebc4f?v=20c00d5ef00e80c8adb5000cca955976" rel="nofollow noopener noreferrer"&gt;Notion database&lt;/a&gt; in a new tab. This is where all saved pages will appear.&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Create Bookmarklet&lt;/strong&gt;: Create a new bookmark in your browser using the JavaScript code below as the URL.&lt;/p&gt;


JavaScript bookmarklet
&lt;div class="highlight highlight-source-js notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;javascript: &lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-k"&gt;async&lt;/span&gt; &lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;


&lt;/li&gt;

&lt;/ol&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/zirkelc/lambdalet" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>showdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Flatten Array of Arrays using JSONata for AWS Step Functions</title>
      <dc:creator>Chris Cook</dc:creator>
      <pubDate>Tue, 17 Jun 2025 14:38:31 +0000</pubDate>
      <link>https://forem.com/aws-builders/flatten-array-of-arrays-using-jsonata-for-aws-step-functions-3klb</link>
      <guid>https://forem.com/aws-builders/flatten-array-of-arrays-using-jsonata-for-aws-step-functions-3klb</guid>
      <description>&lt;p&gt;The &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/state-map.html" rel="noopener noreferrer"&gt;Map state&lt;/a&gt; of AWS Step Functions returns an array of arrays as a result. For further processing, we often need to flatten this result into a simple one-dimensional flat array. With JSONPath, this can be accomplished using the &lt;code&gt;[*]&lt;/code&gt; expression as described in the AWS docs:&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;"ResultSelector"&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;"flattenArray.$"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$[*][*]"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-paths.html#flatten-array-of-arrays" rel="noopener noreferrer"&gt;AWS docs for JSONPath&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Recently, AWS Step Functions introduced JSONata as an alternative query language to JSONPath. However, since this feature is quite new, you won't find much information on how to accomplish the same with JSONata. At least, this section didn't make it into the &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/transforming-data.html" rel="noopener noreferrer"&gt;AWS docs for JSONata&lt;/a&gt; &lt;em&gt;yet&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that I'm using &lt;code&gt;states&lt;/code&gt; instead of &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/transforming-data.html#transforming-reserved-variable-states" rel="noopener noreferrer"&gt;&lt;code&gt;$states&lt;/code&gt;&lt;/a&gt; in the following examples, because the &lt;code&gt;$&lt;/code&gt; indicates a variable but &lt;code&gt;$states&lt;/code&gt; doesn't exist in the context of the &lt;a href="https://try.jsonata.org/bzP3u9-Hc" rel="noopener noreferrer"&gt;JSONata Exerciser&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In JSONata you can use the &lt;code&gt;.*&lt;/code&gt; expression to flatten an array of arrays:&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://try.jsonata.org/bzP3u9-Hc" rel="noopener noreferrer"&gt;JSONata Exerciser&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, you must be aware that this expression returns nothing if the result array is empty:&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://try.jsonata.org/CCLS05be3" rel="noopener noreferrer"&gt;JSONata Exerciser&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In JSONata, you can usually append &lt;code&gt;[]&lt;/code&gt; to an expression to cast the result to an array, even if it has only one value:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2h30v3566o65ewunb3gp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2h30v3566o65ewunb3gp.png" alt="Image description" width="800" height="338"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://try.jsonata.org/L1E0TSgCW" rel="noopener noreferrer"&gt;JSONata Exerciser&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, this trick doesn't work in this case as both &lt;code&gt;[].*&lt;/code&gt; and &lt;code&gt;.*[]&lt;/code&gt; will return nothing:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6wzff9wjb63sk1r4fjcs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6wzff9wjb63sk1r4fjcs.png" alt="Image description" width="800" height="210"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://try.jsonata.org/-P1YAb6Fw" rel="noopener noreferrer"&gt;JSONata Exerciser&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The correct way to handle it is a simple ternary condition using the &lt;a href="https://docs.jsonata.org/boolean-functions#exists" rel="noopener noreferrer"&gt;&lt;code&gt;$exists()&lt;/code&gt;&lt;/a&gt; function:&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://try.jsonata.org/gDX2kgJRZ" rel="noopener noreferrer"&gt;JSONata Exerciser&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the expression &lt;code&gt;$exists(states.result.*)&lt;/code&gt; evaluates to nothing (falsy value), it will return an empty array as a fallback.&lt;/p&gt;

&lt;p&gt;You may ask yourself if there is a shorter version using a short circuit &lt;code&gt;||&lt;/code&gt; or null coalescing &lt;code&gt;??&lt;/code&gt; operator like in JavaScript: &lt;code&gt;states.result.* ?? []&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Unfortunately, this remains an &lt;a href="https://github.com/jsonata-js/jsonata/issues/370" rel="noopener noreferrer"&gt;open issue&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>javascript</category>
    </item>
    <item>
      <title>TIL `$*` captures all positional arguments into a space separated string You can abuse this for a Git alias to avoid typing the commit message in double quotes: Add to .zshrc: alias gc='git add -A &amp;&amp; git commit -m "$*"' Then run: `gc my commit message`</title>
      <dc:creator>Chris Cook</dc:creator>
      <pubDate>Sun, 02 Feb 2025 08:41:49 +0000</pubDate>
      <link>https://forem.com/zirkelc/til-captures-all-positional-arguments-into-a-space-separated-string-you-can-abuse-this-for-7ol</link>
      <guid>https://forem.com/zirkelc/til-captures-all-positional-arguments-into-a-space-separated-string-you-can-abuse-this-for-7ol</guid>
      <description></description>
      <category>todayilearned</category>
      <category>git</category>
    </item>
    <item>
      <title>TypeScript CLI: Automate Build and Deploy Scripts</title>
      <dc:creator>Chris Cook</dc:creator>
      <pubDate>Mon, 27 Jan 2025 11:22:37 +0000</pubDate>
      <link>https://forem.com/zirkelc/typescript-cli-automate-build-and-deploy-scripts-2300</link>
      <guid>https://forem.com/zirkelc/typescript-cli-automate-build-and-deploy-scripts-2300</guid>
      <description>&lt;p&gt;I would like to follow up on my previous post about TypeScript CLIs. Here's how I want to proceed: I plan to implement the &lt;code&gt;build&lt;/code&gt; command to build a Vite app and the &lt;code&gt;deploy&lt;/code&gt; command to deploy the app to Amazon S3 and AWS CloudFront.&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/zirkelc" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F632215%2F83b13af4-ca36-4ceb-8c38-40737fe8087a.jpg" alt="zirkelc"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/zirkelc/creating-a-typescript-cli-for-your-monorepo-5aa" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Creating a TypeScript CLI for Your Monorepo&lt;/h2&gt;
      &lt;h3&gt;Chris Cook ・ Dec 1 '24&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#typescript&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#javascript&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#programming&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;We will use &lt;a href="https://github.com/listr2/listr2" rel="noopener noreferrer"&gt;Listr2&lt;/a&gt; as a task runner to define the steps required to build and deploy the app. We will use &lt;a href="https://github.com/sindresorhus/execa" rel="noopener noreferrer"&gt;execa&lt;/a&gt; to run CLI commands for Vite and AWS. Since we're running TypeScript code, we could use the programmatic APIs instead of CLI commands, but let's keep it simple!&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="cp"&gt;#!/usr/bin/env -S pnpm tsx
&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;chalk&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;chalk&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;Command&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;commander&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;Listr&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;listr2&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;$&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;execa&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Ctx&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;build&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deploy&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;tasks&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;Listr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Ctx&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * Build tasks
     */&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;command&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;build&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deploy&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="s1"&gt;Build&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Listr&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newListr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Ctx&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
          &lt;span class="cm"&gt;/**
           * Runs `vite build`.
           */&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="s2"&gt;`Run &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chalk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;magenta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite build&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;task&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;task&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="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;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;all&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="s2"&gt;`vite build`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stdout&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;cmd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

              &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Build completed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chalk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./dist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;rendererOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;persistentOutput&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="p"&gt;]),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * Deploy tasks
     */&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;command&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deploy&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="s1"&gt;Deploy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Listr&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newListr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Ctx&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
          &lt;span class="cm"&gt;/**
           * Runs `aws s3 sync`.
           */&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="s2"&gt;`Run &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chalk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;magenta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws s3 sync&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;task&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;task&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="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;build&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./dist&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;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;s3://my-bucket&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;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;all&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="s2"&gt;`aws s3 sync &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; --delete`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stdout&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;cmd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

              &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`S3 sync completed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chalk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;rendererOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;persistentOutput&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="cm"&gt;/**
           * Runs `aws cloudfront create-invalidation`.
           */&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="s2"&gt;`Run &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chalk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;magenta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws cloudfront create-invalidation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;task&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;task&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="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;distributionId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;E1234567890ABC&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;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;all&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="s2"&gt;`aws cloudfront create-invalidation --distribution-id &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;distributionId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; --paths /* --no-cli-pager`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stdout&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;cmd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

              &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`CloudFront invalidation completed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chalk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;distributionId&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;rendererOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;persistentOutput&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="p"&gt;]),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rendererOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;collapseSubtasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;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;program&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;Command&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;monorepo&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;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CLI for Monorepo&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;version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="nx"&gt;program&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;build&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;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Build the monorepo&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;action&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;await&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;build&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;program&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deploy&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;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Deploy the monorepo&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;action&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;await&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deploy&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;await&lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseAsync&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;argv&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;tasks&lt;/code&gt; are split into build tasks and deploy tasks. Since deploying requires a build step, we use the &lt;code&gt;enabled&lt;/code&gt; property to conditionally enable the tasks based on the CLI command &lt;code&gt;build&lt;/code&gt; or &lt;code&gt;deploy&lt;/code&gt;. Each task executes the corresponding CLI command and pipes its output to the console.&lt;/p&gt;

&lt;p&gt;Save this script as &lt;code&gt;cli.ts&lt;/code&gt; and run it with &lt;code&gt;pnpm tsx cli&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://asciinema.org/a/DFlVSxmoWDck31dehZZzoDRoH" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fasciinema.org%2Fa%2FDFlVSxmoWDck31dehZZzoDRoH.svg" alt="asciicast" width="690" height="578"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Unique Symbols: How to Use Symbols for Type Safety</title>
      <dc:creator>Chris Cook</dc:creator>
      <pubDate>Wed, 15 Jan 2025 14:09:32 +0000</pubDate>
      <link>https://forem.com/zirkelc/unique-symbols-how-to-use-symbols-for-type-safety-52gb</link>
      <guid>https://forem.com/zirkelc/unique-symbols-how-to-use-symbols-for-type-safety-52gb</guid>
      <description>&lt;p&gt;If you’ve spent some time working with React, you might have stumbled upon React Query’s &lt;a href="https://github.com/TanStack/query/blob/f04dd199eacde5ecb9e50c51c7a894d65aaf35cd/packages/react-query/src/queryOptions.ts#L51-L86" rel="noopener noreferrer"&gt;&lt;code&gt;queryOptions()&lt;/code&gt;&lt;/a&gt; function. Its &lt;a href="https://github.com/TanStack/query/blob/f04dd199eacde5ecb9e50c51c7a894d65aaf35cd/packages/react-query/src/queryOptions.ts#L84-L87" rel="noopener noreferrer"&gt;implementation&lt;/a&gt; looks shockingly simple:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;queryOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&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;options&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, the real magic lies in its &lt;a href="https://github.com/TanStack/query/blob/f04dd199eacde5ecb9e50c51c7a894d65aaf35cd/packages/react-query/src/queryOptions.ts#L51-L82" rel="noopener noreferrer"&gt;overloaded function signatures&lt;/a&gt;. So, what’s so special about it?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you’re unsure what an overloaded function is, you can check out this post: &lt;a href="https://dev.to/zirkelc/how-to-overload-functions-in-typescript-22c3"&gt;Function Overloading: How to Handle Multiple Function Signatures&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Typed Database Queries
&lt;/h3&gt;

&lt;p&gt;Inspired by React Query’s approach, I put together a helper function that might be useful for people working outside of React: a straightforward way to create typed queries, for example, SQL queries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryParamsSymbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unique&lt;/span&gt; &lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryReturnSymbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unique&lt;/span&gt; &lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="nx"&gt;TParams&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&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;TReturn&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;TStatement&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TStatement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;queryParamsSymbol&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;TParams&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;queryReturnSymbol&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;TReturn&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;function&lt;/span&gt; &lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="nx"&gt;TParams&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&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;TReturn&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;TStatement&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TStatement&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TReturn&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;statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;statement&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;Query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TReturn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TStatement&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;Similar to &lt;code&gt;queryOptions()&lt;/code&gt;, the function itself is pretty dull: it takes a SQL statement, wraps it in an object of type &lt;code&gt;Query&lt;/code&gt;, and returns it.&lt;/p&gt;

&lt;p&gt;Here’s a quick example of how you’d call 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getUserById&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;email&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="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT name, email FROM users WHERE id = $id&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;Notice how we pass two types as generic parameters. The first is the required query parameters — in this case, &lt;code&gt;id&lt;/code&gt;. The second represents the query’s return type — &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Under the hood, &lt;code&gt;query()&lt;/code&gt; embeds those two types into the returned object, stashing them in &lt;code&gt;queryParamsSymbol&lt;/code&gt; and &lt;code&gt;queryReturnSymbol&lt;/code&gt;. These symbols are declared as &lt;a href="https://www.typescriptlang.org/docs/handbook/symbols.html#unique-symbol" rel="noopener noreferrer"&gt;&lt;code&gt;unique symbol&lt;/code&gt;&lt;/a&gt;, which means they only exist in type-space and don’t appear in the transpiled JavaScript.&lt;/p&gt;

&lt;p&gt;I use these symbols to temporarily store the parameter and return types and retrieve them whenever I need them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;InferQueryParams&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TQuery&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TQuery&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&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;Params&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UserQueryParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;InferQueryParams&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;getUserById&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;//        ^? { id: number }&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;InferQueryReturn&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TQuery&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TQuery&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;Return&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;Return&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UserQueryReturn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;InferQueryReturn&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;getUserById&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;//        ^? { name: string; email: string }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;InferQueryParams&lt;/code&gt; and &lt;code&gt;InferQueryReturn&lt;/code&gt; are just utility types to confirm that our parameter and return types are being inferred correctly. You might not actually need them, but they’re handy for verifying our approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database Client
&lt;/h3&gt;

&lt;p&gt;Now that we know how to embed parameter and return types in a query object, how do we actually run these queries? Let’s take a look at a simple database client that can execute our typed queries:&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;class&lt;/span&gt; &lt;span class="nc"&gt;DatabaseClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="nx"&gt;TParams&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&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;TReturn&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TReturn&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;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TParams&lt;/span&gt;&lt;span class="p"&gt;,&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="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TReturn&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="c1"&gt;// execute the query and return the results&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="k"&gt;return&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;db&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;DatabaseClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Return type and parameters are inferred from the query object&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getUserById&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;//                                              ^? { id: number }&lt;/span&gt;
&lt;span class="c1"&gt;//      ^? Array&amp;lt;{ name: string; email: string }&amp;gt;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we pass our typed &lt;code&gt;getUserById&lt;/code&gt; query object to the &lt;code&gt;db.execute()&lt;/code&gt; method. Because the &lt;code&gt;Query&lt;/code&gt; type contains both the parameter and return-type information, TypeScript automatically infers them. We can easily confirm this by hovering over &lt;code&gt;result&lt;/code&gt; and  TypeScript will also suggest &lt;code&gt;id&lt;/code&gt; as property of our &lt;code&gt;parameters&lt;/code&gt; object. &lt;/p&gt;

&lt;h3&gt;
  
  
  TypeScript Playground
&lt;/h3&gt;

&lt;p&gt;You can find the complete code example in this &lt;a href="https://www.typescriptlang.org/play/?#code/KYDwDg9gTgLgBAE2AYwDYEMrDsiA7AZ3gEcBXYKATwAVN0BbAgZUvoCMJUAuOUvASzLYCrDqgDcAKFCRYiFBiw58ROEKoAlYDFJQ8Ldpx59B5OCMMTJ08NHgxKYbAEVyVADyS4cACq0oDARwoDDAeAhBWrhQCO5EUPx4AOYANHDoeJQAfHAAvHBR0LHxianpmVkpXr5aOnrBIKHhkShFcTAJyWkZ2XAAPrzhwABmicAIeYNIo3jjVd4+TDDoofRh8CFhEeYdpZMlXZI5+QDe1UQrwGt4MDyLy6vrUt4A2uo0dIwGYgC6d-6BZ5wN5uSi1XT6UScP41bQQqQAXykNlk8GGfGQMH4+DUoM8CwBjAaTW2hRi7U6ZR6xwKrXJBypFXmsLqeGJWxa0WKuy65V6Az40zGE3ygpGwuZ90u1w2jQ5O0p+x5qSOAAoLo8bncltL1gBKHiuCiUdx+T4ENI+cF6HJnbxYVlwE47XVal2a+AI9JBI0eM0BRiW614S06j1ZRHWGR2OAOJxwACSeGGFF9HwDBFNaZpPjT7OacDT7kSKagcEJFr5OQA-OXzXAeLMAG4UKTRuRx7BJ0tp4NZ0E5vObAtFnppEsUWmsmtTiENuDN1vWAD0y7gAFEQAwwKhsO84AB3fgwAAWcDAn20FCCqv4CD15QmDrnnbgqrwDGAaSu6H4qD1ki4IQ8BJNoACqBAUAAQpQCYirixruM6d6NqQ7CTgiaTOh+aw8Ay4jBPQv7cAqewIlkqrVAA5Ew64ADLrgAwj4C6ft+RF-nAABiGgAPIALK8JBUBBAA6gAEuuGjrnAd6TAAJHeVFVHqyKvhBqaghWkzdppxoVu4nYQMMcCgTAGlQDBcERpIq7ePZ9kAHq1shCCoehZYIpI6nCb2cL1PkulQH5rKGY4wDGaZ4HCVZCA2XZDlOS5rG4aRyQET+f54cqcBeYBGAEEEAAiKzoGw6CQYxqD8OsTrVBVlB4MgDQoKQoSmtpw6knS3KUt0TIsnOXWcm0DL9dkFHVN47yGni-qBEG-l4JUU3nua-zmsyBrllAED0PwkHuAAglAAQmlaS1ZLaq12aArWhLGJ57qCj5wM+9SntgWAEKQqAwAQN1rgAdCDq3vWyLw-ECXl5UBqgIGwkyzAecAlcs5WVdV6yqqp1hw-A32-fA+ToAev7wAjQN3cgbXAKqZkWbFWGyW5cAAIy5bjgEqJwwBA6gEBJKqhN-apQA" rel="noopener noreferrer"&gt;TypeScript Playground&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Buffer Logs and Flush Automatically on Error with Powertools for Lambda</title>
      <dc:creator>Chris Cook</dc:creator>
      <pubDate>Wed, 18 Dec 2024 11:40:32 +0000</pubDate>
      <link>https://forem.com/aws-builders/buffer-logs-and-flush-automatically-on-error-with-powertools-for-lambda-k1m</link>
      <guid>https://forem.com/aws-builders/buffer-logs-and-flush-automatically-on-error-with-powertools-for-lambda-k1m</guid>
      <description>&lt;p&gt;The latest release of &lt;a href="https://docs.powertools.aws.dev/lambda/typescript/latest/" rel="noopener noreferrer"&gt;AWS Powertools for Lambda&lt;/a&gt; makes it easier to extend the Logger with custom functionality:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Logger class is now more extensible&lt;/strong&gt;&lt;br&gt;
You can now overwrite the Logger methods createAndPopulateLogItem, printLog, and processLogItem, which were previously private. This allows you to extend the Logger and add new functionality, e.g., implement your own message buffer.&lt;br&gt;
&lt;a href="https://github.com/aws-powertools/powertools-lambda-typescript/releases/tag/v2.12.0" rel="noopener noreferrer"&gt;Release v2.12.0&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'm using this new extensibility to implement a useful feature: log buffering and flushing. The idea is simple: in a production environment, we usually only log important information, such as warnings and errors, because log space can get expensive and it creates noise. However, when an error occurs, we want every possible piece of information: all those debug and info logs scattered throughout a function should be available. But they are not because we set our log level too low.&lt;/p&gt;

&lt;h2&gt;
  
  
  Buffer and Flush
&lt;/h2&gt;

&lt;p&gt;What if we collect all these debug and info logs internally, and if an important event occurs like an error, we print them to the console? I classify logs into two categories: low-level logs and high-level logs. If our configured log level is &lt;code&gt;WARN&lt;/code&gt;, a &lt;code&gt;DEBUG&lt;/code&gt; or &lt;code&gt;INFO&lt;/code&gt; log would be low-level, and &lt;code&gt;ERROR&lt;/code&gt; would be a high-level log.&lt;/p&gt;

&lt;p&gt;Now when we print a low-level log, instead of discarding it as it is now, we buffer the log in an internal list. As soon as we have a high-level log, we flush all buffered logs to the console.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;To add this functionality, we create a new class that extends the &lt;code&gt;Logger&lt;/code&gt; class from Powertools and override &lt;code&gt;processLogItem()&lt;/code&gt;. This is the central method which is called by the different log methods like &lt;code&gt;logger.debug()&lt;/code&gt;. The &lt;a href="https://github.com/aws-powertools/powertools-lambda-typescript/blob/955df73c0df61874b2f664602138713ecb21fb8b/packages/logger/src/Logger.ts#L865-L880" rel="noopener noreferrer"&gt;original implementation&lt;/a&gt; prints the log item to the console if it's at the right level. By overriding this method, we can add our special logic of buffering and flushing logs depending on the log level.&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;LogItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Logger&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;PowertoolsLogger&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;@aws-lambda-powertools/logger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LogItemExtraInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LogItemMessage&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;@aws-lambda-powertools/logger/types&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;class&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;PowertoolsLogger&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&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="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="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nx"&gt;override&lt;/span&gt; &lt;span class="nf"&gt;processLogItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LogItemMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;extraInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LogItemExtraInput&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;xRayTraceId&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;envVarsService&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;getXrayTraceId&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Flush buffer when log level is higher than the configured log level&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;logLevel&lt;/span&gt; &lt;span class="o"&gt;&amp;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;level&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;xRayTraceId&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;buffer&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="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;xRayTraceId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

      &lt;span class="c1"&gt;// Print all log items in the buffer&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;buffer&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Flushing buffer with &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;buffer&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="s2"&gt; log items`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;for &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;bufferLogLevel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bufferLogItem&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Create a new LogItem from the stringified log item&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;printLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bufferLogLevel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LogItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bufferLogItem&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// Clear the buffer after flushing&lt;/span&gt;
      &lt;span class="c1"&gt;// This also removes entries from other X-Ray trace IDs&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Buffer the log item when log level is lower than the configured log level&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;logLevel&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;level&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;xRayTraceId&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;buffer&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="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;xRayTraceId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
      &lt;span class="c1"&gt;// Add the stringified log item to the buffer&lt;/span&gt;
      &lt;span class="c1"&gt;// Serializing the log item ensures it is not mutated after being added to the buffer&lt;/span&gt;
      &lt;span class="nx"&gt;buffer&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;logLevel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&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="nf"&gt;createAndPopulateLogItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logLevel&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;extraInput&lt;/span&gt;&lt;span class="p"&gt;))]);&lt;/span&gt;

      &lt;span class="c1"&gt;// Update the buffer with the new log item&lt;/span&gt;
      &lt;span class="c1"&gt;// This also removes other X-Ray trace IDs from the buffer&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;xRayTraceId&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;buffer&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;// Call the parent method to ensure the log item is processed&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;processLogItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logLevel&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;extraInput&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;You might ask why we use the X-Ray Trace ID here. It's common to instantiate the &lt;code&gt;Logger&lt;/code&gt; outside of the handler function. However, because the Lambda execution environment is re-used for potentially multiple invocations, the buffer could contain log items from previous invocations. That's the reason the buffer is implemented as an object rather than a simple array. We use the X-Ray Trace ID as an identifier to only buffer log items from the same invocation.&lt;br&gt;
The &lt;code&gt;buffer&lt;/code&gt; is implemented as an object rather than a simple array. When the buffer is flushed, we can simply reset the object and therefore purge items from other invocations.&lt;/p&gt;
&lt;h2&gt;
  
  
  Test Locally
&lt;/h2&gt;

&lt;p&gt;Let's quickly verify the implementation locally:&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;// set X-Ray Trace ID manually if running locally&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;_X_AMZN_TRACE_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1-abcdef12-3456abcdef123456abcdef12&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// log level = WARN&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logger&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;Logger&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;logLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WARN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;debug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt; log level&lt;/span&gt;
&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// &amp;lt; log level&lt;/span&gt;
&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;warn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// = log level&lt;/span&gt;
&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// &amp;gt; log level&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the output we get:&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="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"WARN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"warn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"sampling_rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"service_undefined"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2024-12-18T11:01:55.260Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"xray_trace_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"1-abcdef12-3456abcdef123456abcdef12"&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="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"INFO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Flushing buffer with 2 log items"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"sampling_rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"service_undefined"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2024-12-18T11:01:55.261Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"xray_trace_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"1-abcdef12-3456abcdef123456abcdef12"&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="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"DEBUG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"debug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"sampling_rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"service_undefined"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2024-12-18T11:01:55.254Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"xray_trace_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"1-abcdef12-3456abcdef123456abcdef12"&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="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"INFO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"sampling_rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"service_undefined"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2024-12-18T11:01:55.259Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"xray_trace_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"1-abcdef12-3456abcdef123456abcdef12"&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="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"ERROR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"sampling_rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"service_undefined"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2024-12-18T11:01:59.228Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"xray_trace_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"1-abcdef12-3456abcdef123456abcdef12"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The warning is the first message because the debug and info logs were buffered. When the error was logged, we flushed the buffered logs (and printed an info) before the error was actually printed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Request For Comments
&lt;/h2&gt;

&lt;p&gt;My naive implementation has a few caveats. Most importantly, the buffer size is not limited, which means it could cause memory issues if the buffer grows too large. There are a few approaches to mitigate this issue, for example, by implementing the buffer as a sliding window which only keeps the most recent logs or limiting the total buffer size.&lt;/p&gt;

&lt;p&gt;Furthermore, the buffered logs are only flushed in controlled cases like on &lt;code&gt;logger.error()&lt;/code&gt;, but not on unhandled errors. This behavior could easily be achieved if we make the buffer public and use a middleware like Middy.js. Middy exposes an &lt;a href="https://middy.js.org/docs/writing-middlewares/intro" rel="noopener noreferrer"&gt;&lt;code&gt;onError&lt;/code&gt;&lt;/a&gt; event that we could utilize to flush the buffer.&lt;/p&gt;

&lt;p&gt;I have written about this more extensively in this &lt;a href="https://github.com/aws-powertools/powertools-lambda-typescript/discussions/3410" rel="noopener noreferrer"&gt;Request For Comment&lt;/a&gt; on the official AWS Powertools for Lambda repository.&lt;/p&gt;

&lt;p&gt;If you'd like to see this feature become a part of Powertools for Lambda, please share your ideas and feedback there 🙏&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>How To Remove Dynamic Values From Snapshot With Serializers</title>
      <dc:creator>Chris Cook</dc:creator>
      <pubDate>Sat, 14 Dec 2024 09:17:27 +0000</pubDate>
      <link>https://forem.com/zirkelc/remove-dynamic-values-from-snapshot-with-serializers-1857</link>
      <guid>https://forem.com/zirkelc/remove-dynamic-values-from-snapshot-with-serializers-1857</guid>
      <description>&lt;p&gt;Snapshot tests in &lt;a href="https://jestjs.io/docs/snapshot-testing" rel="noopener noreferrer"&gt;Jest&lt;/a&gt; and &lt;a href="https://vitest.dev/guide/snapshot" rel="noopener noreferrer"&gt;Vitest&lt;/a&gt; are powerful tools for detecting unexpected changes in your code's output. However, they easily break when dealing with dynamic values like generated IDs or timestamps that change with each test run. While mocking these values is possible, it can lead to unintended side effects.&lt;/p&gt;

&lt;p&gt;Consider this user object which could be returned from an API call or database query:&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&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="s2"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&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;Every time you run your tests, the &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;createdAt&lt;/code&gt; values will be different, causing your snapshots to fail. &lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Implementation
&lt;/h2&gt;

&lt;p&gt;Here's how to create a custom serializer that replaces dynamic values with consistent placeholders:&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;property&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&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;placeholder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[ID]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSnapshotSerializer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&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;val&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasOwn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;placeholder&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nf"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;indentation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;printer&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="nf"&gt;printer&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;val&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;indentation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;refs&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;You can add a custom snapshot serializer with &lt;code&gt;expect.addSnapshotSerializer()&lt;/code&gt;.&lt;br&gt;
It expects an object with two functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;test()&lt;/code&gt; is used to determine whether this custom serializer should be used. It checks if the value from &lt;code&gt;expect(value)&lt;/code&gt; is an object with the property and has not been replaced by the placeholder.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;serialize()&lt;/code&gt; is only called if &lt;code&gt;test()&lt;/code&gt; has returned true. It replaces the property with the placeholder and calls the &lt;code&gt;printer()&lt;/code&gt; function to serialize the value into a JSON-like string.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Tests
&lt;/h2&gt;

&lt;p&gt;Now, when you run your tests, you will see that &lt;code&gt;id&lt;/code&gt; was replaced with the &lt;code&gt;[ID]&lt;/code&gt; placeholder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&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;name&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;createdAt&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="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSnapshotSerializer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;snapshot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123e4567-e89b-12d3-a456-426614174000&lt;/span&gt;&lt;span class="dl"&gt;'&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;John Doe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2024-03-20T12:00:00Z&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;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toMatchInlineSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
    {
      "id": "[ID]",
      "name": "John Doe",
    }
  `&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;
  
  
  Making it Reusable
&lt;/h2&gt;

&lt;p&gt;What if we need to handle multiple dynamic properties? Let's create a reusable solution:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;replaceProperty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;property&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;placeholder&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="nx"&gt;SnapshotSerializer&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="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&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;val&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasOwn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;placeholder&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;indentation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;printer&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="nf"&gt;printer&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;val&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;indentation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;refs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In your tests, you can create multiple serializers for different properties:&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;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSnapshotSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;replaceProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[ID]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSnapshotSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;replaceProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;createdAt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[TIMESTAMP]&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;I use these serializers so frequently that I created the npm package &lt;a href="https://www.npmjs.com/package/snapshot-serializers" rel="noopener noreferrer"&gt;snapshot-serializers&lt;/a&gt; to make it easier for everyone.&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;replaceProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;removeProperty&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;snapshot-serializers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&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;name&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;createdAt&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;password&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Type-safe property replacement&lt;/span&gt;
&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSnapshotSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;// TypeScript will only allow "id" | "name" | "createdAt" | "password"&lt;/span&gt;
  &lt;span class="nx"&gt;replaceProperty&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&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;property&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[ID]&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;// Remove properties entirely&lt;/span&gt;
&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSnapshotSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;removeProperty&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&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;property&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password&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;// This would cause a TypeScript error:&lt;/span&gt;
&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSnapshotSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;replaceProperty&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&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;property&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invalid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// Error: Type '"invalid"' is not assignable...&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 provides a type-safe API to replace or remove properties in your snapshots. You can provide a generic type parameter like &lt;code&gt;removeProperty&amp;lt;User&amp;gt;()&lt;/code&gt; and the function will suggest all possible property names based on the &lt;code&gt;User&lt;/code&gt; type. Any other property will cause a TypeScript error.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>testing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Creating a TypeScript CLI for Your Monorepo</title>
      <dc:creator>Chris Cook</dc:creator>
      <pubDate>Sun, 01 Dec 2024 13:29:13 +0000</pubDate>
      <link>https://forem.com/zirkelc/creating-a-typescript-cli-for-your-monorepo-5aa</link>
      <guid>https://forem.com/zirkelc/creating-a-typescript-cli-for-your-monorepo-5aa</guid>
      <description>&lt;p&gt;I like to create local CLIs for my Monorepo to automate tasks like &lt;code&gt;build&lt;/code&gt; and &lt;code&gt;deploy&lt;/code&gt;. These tasks often require more than just chaining a few commands in an npm script (like &lt;code&gt;rimraf dist &amp;amp;&amp;amp; tsc&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Using &lt;a href="https://github.com/tj/commander.js" rel="noopener noreferrer"&gt;commander.js&lt;/a&gt; and &lt;a href="https://tsx.is/" rel="noopener noreferrer"&gt;tsx&lt;/a&gt;, we can create executable programs written in TypeScript that run from the command line like any other CLI tool.&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="cp"&gt;#!/usr/bin/env -S pnpm tsx
&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;Command&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;commander&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;program&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;Command&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;monorepo&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;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CLI for Monorepo&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;version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="nx"&gt;program&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;build&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;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Build the monorepo&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;action&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Building...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// run your build steps ...&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;program&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deploy&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;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Deploy the monorepo&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;action&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Deploying...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// run your deploy steps ...&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;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseAsync&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;argv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save this script as &lt;code&gt;cli&lt;/code&gt; (or any name you prefer) in your project root and make it executable with &lt;code&gt;chmod +x cli&lt;/code&gt;. You can then run it directly using &lt;code&gt;./cli&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;./cli
Usage: monorepo &lt;span class="o"&gt;[&lt;/span&gt;options] &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

CLI &lt;span class="k"&gt;for &lt;/span&gt;Monorepo

Options:
  &lt;span class="nt"&gt;-V&lt;/span&gt;, &lt;span class="nt"&gt;--version&lt;/span&gt;   output the version number
  &lt;span class="nt"&gt;-h&lt;/span&gt;, &lt;span class="nt"&gt;--help&lt;/span&gt;      display &lt;span class="nb"&gt;help &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;command

&lt;/span&gt;Commands:
  build           Build the monorepo
  deploy          Deploy the monorepo
  &lt;span class="nb"&gt;help&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;  display &lt;span class="nb"&gt;help &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The magic that allows you to run this without &lt;code&gt;node&lt;/code&gt;, &lt;code&gt;npx&lt;/code&gt;, or even a &lt;code&gt;.ts&lt;/code&gt; extension is in the first line - the shebang:&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;#!/usr/bin/env -S pnpm tsx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shebang tells your shell which program should execute this file. Behind the scenes, it translates your &lt;code&gt;./cli&lt;/code&gt; command into &lt;code&gt;pnpm tsx cli&lt;/code&gt;. This works with other &lt;a href="https://tsx.is/shell-scripts" rel="noopener noreferrer"&gt;package managers&lt;/a&gt; too - you can use &lt;code&gt;npm&lt;/code&gt; or &lt;code&gt;yarn&lt;/code&gt; instead of &lt;code&gt;pnpm&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Middleware for Step Functions: Automatically Store and Load Payloads from Amazon S3</title>
      <dc:creator>Chris Cook</dc:creator>
      <pubDate>Tue, 20 Aug 2024 09:01:50 +0000</pubDate>
      <link>https://forem.com/aws-builders/middleware-for-step-functions-automatically-store-and-load-payloads-from-amazon-s3-18g4</link>
      <guid>https://forem.com/aws-builders/middleware-for-step-functions-automatically-store-and-load-payloads-from-amazon-s3-18g4</guid>
      <description>&lt;p&gt;I would like to introduce &lt;code&gt;middy-store&lt;/code&gt;, a new library I built over the last couple of months. I've been pondering this idea for a while, going back to this &lt;a href="https://github.com/middyjs/middy/issues/1066" rel="noopener noreferrer"&gt;feature request&lt;/a&gt; I opened more than a year ago. &lt;code&gt;middy-store&lt;/code&gt; is a middleware for Middy that automatically stores and loads payloads from and to a Store like Amazon S3 or potentially other services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;AWS services have certain limits that one must be aware of. For example, AWS Lambda has a &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html" rel="noopener noreferrer"&gt;payload limit&lt;/a&gt; of 6MB for synchronous invocations and 256KB for asynchronous invocations. AWS Step Functions allows for a &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/service-quotas.html#service-limits-state-machine-executions" rel="noopener noreferrer"&gt;maximum input or output size&lt;/a&gt; of 256KB of data as a UTF-8 encoded string. If you exceed this limit when returning data, you will encounter the infamous &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/concepts-error-handling.html#error-data-limit-exceed" rel="noopener noreferrer"&gt;&lt;code&gt;States.DataLimitExceeded&lt;/code&gt;&lt;/a&gt; exception. &lt;/p&gt;

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

&lt;p&gt;The usual workaround for this limitation is to check the size of your payload and save it temporarily in persistent storage such as Amazon S3. Then, you return the object URL or ARN for S3. The next Lambda checks if there is a URL or ARN in the input and loads the payload from S3. As one can imagine, this results in a lot of boilerplate code to store and load the payload from and to Amazon S3, which has to be repeated in every Lambda. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevycm8daut8y82jh8rs0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevycm8daut8y82jh8rs0.png" alt="Lambda Workflow with Upload and Download to S3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This becomes even more cumbersome when you only want to save part of the payload to S3 and leave the rest as is. For example, when working with Step Functions, the payload could contain control flow data for states like &lt;code&gt;Choice&lt;/code&gt; or &lt;code&gt;Map&lt;/code&gt;, which has to be accessed directly. This means the first Lambda saves a partial payload to S3, and the next Lambda has to load the partial payload from S3 and merge it with the rest of the payload. This requires ensuring that the types are consistent across multiple functions, which is, of course, very error-prone.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;middy-store&lt;/code&gt; is a middleware for Middy. It's attached to a Lambda function and is called twice during a Lambda invocation: &lt;em&gt;before&lt;/em&gt; and &lt;em&gt;after&lt;/em&gt; the Lambda &lt;code&gt;handler()&lt;/code&gt; runs. It receives the input before the handler runs and receives the output from the handler after it has finished. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdcvbli8dufk6tc6q0t3o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdcvbli8dufk6tc6q0t3o.png" alt="Data flowing through a Middleware"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's start at the end with the output &lt;em&gt;after&lt;/em&gt; a successful invocation to make it easier to follow: &lt;code&gt;middy-store&lt;/code&gt; receives the output (the payload) from the &lt;code&gt;handler()&lt;/code&gt; function and checks the size. To calculate the size, it stringifies the payload, if it is an object, and uses &lt;code&gt;Buffer.byteLength()&lt;/code&gt; to calculate the UTF-8 encoded string size. If the size is larger than a certain configurable threshold, the payload is stored in a Store like Amazon S3. The reference to the stored payload (e.g., an S3 URL or ARN) is then returned as the output instead of the original output.&lt;/p&gt;

&lt;p&gt;Now let's look at the next Lambda function (e.g. in a state machine), which will receive this output as its input. This time we are looking at the input &lt;em&gt;before&lt;/em&gt; the &lt;code&gt;handler()&lt;/code&gt; is invoked: &lt;code&gt;middy-store&lt;/code&gt; receives the input to the handler and searches for a reference to a stored payload. If it finds one, the payload is loaded from the Store and returned as the input to the handler. The handler uses the payload as if it was passed directly to it.&lt;/p&gt;

&lt;p&gt;Here's an example to illustrate how &lt;code&gt;middy-store&lt;/code&gt; works:&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="cm"&gt;/* ./src/functions/handler1.ts */&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;handler1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;middy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;middyStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;stores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;S3Store&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="cm"&gt;/* S3 options */&lt;/span&gt; &lt;span class="p"&gt;})],&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handler&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;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;// Return 1MB of random data as a base64 encoded string as output &lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&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="cm"&gt;/* ./src/functions/handler2.ts */&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;handler2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;middy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;middyStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;stores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;S3Store&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="cm"&gt;/* S3 options */&lt;/span&gt; &lt;span class="p"&gt;})],&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handler&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;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;// Print the size of the input&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Size: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Buffer&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;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;byteLength&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; MB`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="cm"&gt;/* ./src/workflow.ts */&lt;/span&gt;
&lt;span class="c1"&gt;// First Lambda returns a large output&lt;/span&gt;
&lt;span class="c1"&gt;// It automatically uploads the data to S3 &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;output1&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;handler1&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;

&lt;span class="c1"&gt;// Output is a reference to the S3 object: { "@middy-store": "s3://bucket/key"}&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;output1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

&lt;span class="c1"&gt;// Second Lambda receives the output as input&lt;/span&gt;
&lt;span class="c1"&gt;// It automatically downloads the data from S3&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;output2&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;handler2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;output1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  What's a Store?
&lt;/h3&gt;

&lt;p&gt;In general, a Store is any service that allows you to store and load arbitrary payloads, like Amazon S3 or other persistent storage systems. Databases like DynamoDB can also act as a Store. The Store receives a payload from the Lambda handler, serializes it (if it's an object), and stores it in persistent storage. When the next Lambda handler needs the payload, the Store loads the payload from the storage, deserializes and returns it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;middy-store&lt;/code&gt; interacts with a Store through a &lt;code&gt;StoreInterface&lt;/code&gt; interface, which every Store has to implement. The interface defines the functions &lt;code&gt;canStore()&lt;/code&gt; and &lt;code&gt;store()&lt;/code&gt; to store payloads, and &lt;code&gt;canLoad()&lt;/code&gt; and &lt;code&gt;load()&lt;/code&gt; to load payloads.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;StoreInterface&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TReference&lt;/span&gt; &lt;span class="o"&gt;=&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;name&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;canLoad&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoadArgs&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;load&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoadArgs&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TReference&lt;/span&gt; &lt;span class="o"&gt;|&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="o"&gt;=&amp;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;TPayload&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;canStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StoreArgs&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TPayload&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StoreArgs&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TPayload&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TReference&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;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;canStore()&lt;/code&gt; serves as a guard to check if the Store can store a given payload. It receives the payload and its byte size and checks if the payload fits within the maximum size limits of the Store. For example, a Store backed by DynamoDB has a maximum item size of 400KB, while an S3 store has effectively no limit on the payload size it can store.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;store()&lt;/code&gt; receives a payload and stores it in its underlying storage system. It returns a reference to the payload, which is a unique identifier to identify the stored payload within the underlying service. For example, the Amazon S3 Store uses an S3 URI in the format &lt;code&gt;s3://&amp;lt;bucket&amp;gt;/&amp;lt;key&amp;gt;&lt;/code&gt; as a reference, while other Amazon services might use ARNs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;canLoad()&lt;/code&gt; acts like a filter to check if the Store can load a certain reference. It receives the reference to a stored payload and checks if it's a valid identifier for the underlying storage system. For example, the Amazon S3 Store checks if the reference is a valid S3 URI, while a DynamoDB Store would check if it's a valid ARN.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;load()&lt;/code&gt; receives the reference to a stored payload and loads the payload from storage. Depending on the Store, the payload will be deserialized into its original type according to the metadata that was stored alongside it. For example, a payload of type &lt;code&gt;application/json&lt;/code&gt; will get parsed back into a JSON object, while a plain string of type &lt;code&gt;text/plain&lt;/code&gt; will remain unaltered.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Single and Multiple Stores
&lt;/h3&gt;

&lt;p&gt;Most of the time, you will only need one Store, like Amazon S3, which can effectively store any payload. However, &lt;code&gt;middy-store&lt;/code&gt; lets you work with multiple Stores at the same time. This can be useful if you want to store different types of payloads in different Stores. For example, you might want to store large payloads in S3 and small payloads in DynamoDB.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;middy-store&lt;/code&gt; accepts an &lt;code&gt;Array&amp;lt;StoreInterface&amp;gt;&lt;/code&gt; in the options to provide one or more Stores. When &lt;code&gt;middy-store&lt;/code&gt; runs &lt;em&gt;before&lt;/em&gt; the handler and finds a reference in the input, it will iterate over the Stores and call &lt;code&gt;canLoad()&lt;/code&gt; with the reference for each Store. The first Store that returns &lt;code&gt;true&lt;/code&gt; will be used to load the payload with &lt;code&gt;load()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On the other hand, when &lt;code&gt;middy-store&lt;/code&gt; runs &lt;em&gt;after&lt;/em&gt; the handler and the output is larger than the maximum allowed size, it will iterate over the Stores and call &lt;code&gt;canStore()&lt;/code&gt; for each Store. The first Store that returns &lt;code&gt;true&lt;/code&gt; will be used to store the payload with &lt;code&gt;store()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Therefore, it is important to note that the order of the Stores in the array is important. &lt;/p&gt;
&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;p&gt;When a payload is stored in a Store, &lt;code&gt;middy-store&lt;/code&gt; will return a reference to the stored payload. The reference is a unique identifier to find the stored payload in the Store. The value of the identifier depends on the Store and its configuration. For example, the Amazon S3 Store will use an S3 URI by default. However, it can also be configured to return other formats like an ARN &lt;code&gt;arn:aws:s3:::&amp;lt;bucket&amp;gt;/&amp;lt;key&amp;gt;&lt;/code&gt;, an HTTP endpoint &lt;code&gt;https://&amp;lt;bucket&amp;gt;.s3.us-west-1.amazonaws.com/&amp;lt;key&amp;gt;&lt;/code&gt;, or a structured object with the &lt;code&gt;bucket&lt;/code&gt; and &lt;code&gt;key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The output from the handler &lt;em&gt;after&lt;/em&gt; &lt;code&gt;middy-store&lt;/code&gt; will contain the reference to the stored payload:&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="cm"&gt;/* Output with reference */&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@middy-store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;s3://bucket/key&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;&lt;code&gt;middy-store&lt;/code&gt; embeds the reference from the Store in the output as an object with a key &lt;code&gt;"@middy-store"&lt;/code&gt;. This allows &lt;code&gt;middy-store&lt;/code&gt; to quickly find all references when the next Lambda function is called and load the payloads from the Store &lt;em&gt;before&lt;/em&gt; the handler runs. In case you are wondering, &lt;code&gt;middy-store&lt;/code&gt; recursively iterates through the input object and searches for the &lt;code&gt;"@middy-store"&lt;/code&gt; key. That means the input can contain multiple references, even from different Stores, and &lt;code&gt;middy-store&lt;/code&gt; will find and load them. &lt;/p&gt;
&lt;h3&gt;
  
  
  Selecting a Payload
&lt;/h3&gt;

&lt;p&gt;By default, &lt;code&gt;middy-store&lt;/code&gt; will store the entire output of the handler as a payload in the Store. However, you can also select only a part of the output to be stored. This is useful for workflows like AWS Step Functions, where you might need some of the data for control flow, e.g., a &lt;code&gt;Choice&lt;/code&gt; state.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;middy-store&lt;/code&gt; accepts a &lt;code&gt;selector&lt;/code&gt; in its &lt;code&gt;storingOptions&lt;/code&gt; config. The &lt;code&gt;selector&lt;/code&gt; is a string path to the relevant value in the output that should be stored.&lt;/p&gt;

&lt;p&gt;Here's an example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;foo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;baz&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;middy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;middyStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;stores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;S3Store&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="cm"&gt;/* S3 options */&lt;/span&gt; &lt;span class="p"&gt;})],&lt;/span&gt;
      &lt;span class="na"&gt;storingOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;selector&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="cm"&gt;/* select the entire output as payload */&lt;/span&gt;
        &lt;span class="c1"&gt;// selector: 'a';      /* selects the payload at the path 'a' */&lt;/span&gt;
        &lt;span class="c1"&gt;// selector: 'a.b';    /* selects the payload at the path 'a.b' */&lt;/span&gt;
        &lt;span class="c1"&gt;// selector: 'a.b[0]'; /* selects the payload at the path 'a.b[0]' */&lt;/span&gt;
        &lt;span class="c1"&gt;// selector: 'a.b[*]'; /* selects the payloads at the paths 'a.b[0]', 'a.b[1]', 'a.b[2]', etc. */&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handler&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="nx"&gt;output&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;handler&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The default selector is an empty string (or undefined), which selects the entire output as a payload. In this case, &lt;code&gt;middy-store&lt;/code&gt; will return an object with only one property, which is the reference to the stored payload.&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="cm"&gt;/* selector: '' */&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@middy-store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;s3://bucket/key&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 selectors &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;a.b&lt;/code&gt;, or &lt;code&gt;a.b[0]&lt;/code&gt; select the value at the path and store only this part in the Store. The reference to the stored payload will be inserted at the path in the output, thereby replacing the original value.&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="cm"&gt;/* selector: 'a' */&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@middy-store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;s3://bucket/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="cm"&gt;/* selector: 'a.b' */&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@middy-store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;s3://bucket/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="p"&gt;}&lt;/span&gt;
&lt;span class="cm"&gt;/* selector: 'a.b[0]' */&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;b&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@middy-store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;s3://bucket/key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; 
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;baz&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;p&gt;A selector ending with &lt;code&gt;[*]&lt;/code&gt; like &lt;code&gt;a.b[*]&lt;/code&gt; acts like an iterator. It will select the array at &lt;code&gt;a.b&lt;/code&gt; and store each element in the array in the Store separately. Each element will be replaced with the reference to the stored payload.&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="cm"&gt;/* selector: 'a.b[*]' */&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;b&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@middy-store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;s3://bucket/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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@middy-store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;s3://bucket/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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@middy-store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;s3://bucket/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="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;
  
  
  Size Limit
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;middy-store&lt;/code&gt; will calculate the size of the entire output returned from the handler. The size is calculated by stringifying the output, if it's not already a string, and calculating the UTF-8 encoded size of the string in bytes. It will then compare this size to the configured &lt;code&gt;minSize&lt;/code&gt; in the &lt;code&gt;storingOptions&lt;/code&gt; config. If the output size is equal to or greater than the &lt;code&gt;minSize&lt;/code&gt;, it will store the output or a part of it in the Store.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;middy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;middyStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;stores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;S3Store&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="cm"&gt;/* S3 options */&lt;/span&gt; &lt;span class="p"&gt;})],&lt;/span&gt;
      &lt;span class="na"&gt;storingOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;minSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Sizes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STEP_FUNCTIONS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="cm"&gt;/* 256KB */&lt;/span&gt;
        &lt;span class="c1"&gt;// minSize: Sizes.LAMBDA_SYNC,  /* 6MB */&lt;/span&gt;
        &lt;span class="c1"&gt;// minSize: Sizes.LAMBDA_ASYNC, /* 256KB */&lt;/span&gt;
        &lt;span class="c1"&gt;// minSize: 1024 * 1024,        /* 1MB */&lt;/span&gt;
        &lt;span class="c1"&gt;// minSize: Sizes.ZERO,         /* 0 */&lt;/span&gt;
        &lt;span class="c1"&gt;// minSize: Sizes.INFINITY,     /* Infinity */&lt;/span&gt;
        &lt;span class="c1"&gt;// minSize: Sizes.kb(512),      /* 512KB */&lt;/span&gt;
        &lt;span class="c1"&gt;// minSize: Sizes.mb(1),        /* 1MB */&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handler&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="nx"&gt;output&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;handler&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;middy-store&lt;/code&gt; provides a &lt;code&gt;Sizes&lt;/code&gt; helper with some predefined limits for Lambda and Step Functions. If &lt;code&gt;minSize&lt;/code&gt; is not specified, it will use &lt;code&gt;Sizes.STEP_FUNCTIONS&lt;/code&gt; with 256KB as the default minimum size. The &lt;code&gt;Sizes.ZERO&lt;/code&gt; (equal to the number 0) means that &lt;code&gt;middy-store&lt;/code&gt; will always store the payload in a Store, ignoring the actual output size. On the other hand, &lt;code&gt;Sizes.INFINITY&lt;/code&gt; (equal to &lt;code&gt;Math.POSITIVE_INFINITY&lt;/code&gt;) means that it will never store the payload in a Store.&lt;/p&gt;
&lt;h2&gt;
  
  
  Stores
&lt;/h2&gt;

&lt;p&gt;Currently, there is only one Store implementation for Amazon S3, but I'm planning to implement a Store backed by DynamoDB and DAX. DynamoDB, with its Time-To-Live (TTL) feature, provides a great option for short-term payloads that only need to exist during the execution of a workflow like Step Functions.&lt;/p&gt;
&lt;h3&gt;
  
  
  Amazon S3
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;middy-store-s3&lt;/code&gt; package provides a store implementation for Amazon S3. It uses the official &lt;code&gt;@aws-sdk/client-s3&lt;/code&gt; package to interact with S3.&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;middyStore&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;middy-store&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;S3Store&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;middy-store-s3&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;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;middy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;middyStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;stores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;S3Store&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;us-east-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bucket&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
          &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;arn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handler&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;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="cm"&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;S3Store&lt;/code&gt; only requires a &lt;code&gt;bucket&lt;/code&gt; where the payloads are being stored. The &lt;code&gt;key&lt;/code&gt; is optional and defaults to &lt;code&gt;randomUUID()&lt;/code&gt;. The &lt;code&gt;format&lt;/code&gt; configures the style of the reference that is returned after a payload is stored. The supported formats include &lt;code&gt;arn&lt;/code&gt;, &lt;code&gt;object&lt;/code&gt;, or one of the URL formats from the &lt;a href="https://www.npmjs.com/package/amazon-s3-url" rel="noopener noreferrer"&gt;amazon-s3-url&lt;/a&gt; package. It's important to note that &lt;code&gt;S3Store&lt;/code&gt; can load any of these formats; the &lt;code&gt;format&lt;/code&gt; config only concerns the returned reference. The &lt;code&gt;config&lt;/code&gt; is the &lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-client-s3/Interface/S3ClientConfig/" rel="noopener noreferrer"&gt;S3 client configuration&lt;/a&gt; and is optional. If not set, the S3 client will resolve the config (credentials, region, etc.) from the environment or file system.&lt;/p&gt;
&lt;h3&gt;
  
  
  Custom Store
&lt;/h3&gt;

&lt;p&gt;A new Store can be implemented as a class or a plain object, as long as it provides the required functions from the &lt;code&gt;StoreInterface&lt;/code&gt; interface.&lt;/p&gt;

&lt;p&gt;Here's an example of a Store to store and load payloads as base64 encoded &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs#datatextplainbase64sgvsbg8sifdvcmxkiq" rel="noopener noreferrer"&gt;data URLs&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;StoreInterface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;middyStore&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;middy-store&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;base64Store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StoreInterface&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&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="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="cm"&gt;/* Reference must be a string starting with "data:text/plain;base64," */&lt;/span&gt;
  &lt;span class="na"&gt;canLoad&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;reference&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;reference&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data:text/plain;base64,&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="cm"&gt;/* Decode base64 string and parse into object */&lt;/span&gt;
  &lt;span class="na"&gt;load&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;reference&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;base64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reference&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data:text/plain;base64,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Buffer&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;base64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="cm"&gt;/* Payload must be a string or an object */&lt;/span&gt;
  &lt;span class="na"&gt;canStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&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="cm"&gt;/* Stringify object and encode as base64 string */&lt;/span&gt;
  &lt;span class="na"&gt;store&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;payload&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&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;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&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="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&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="s2"&gt;`data:text/plain;base64,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;middy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;middyStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;stores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;base64Store&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;storingOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;minSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Sizes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ZERO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* Always store the data */&lt;/span&gt; 
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handler&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;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="cm"&gt;/* Random text with 100 words */&lt;/span&gt; 
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.`&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;output&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;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/* Prints: { '@middy-store': 'data:text/plain;base64,IkxvcmVtIGlwc3VtIGRvbG9yIHNpdC...' } */&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This example is the perfect way to try &lt;code&gt;middy-store&lt;/code&gt;, because it doesn't rely on external resources like an S3 bucket. You will find it in the repository at &lt;a href="https://github.com/zirkelc/middy-store/blob/main/examples/custom-store/index.ts" rel="noopener noreferrer"&gt;examples/custom-store&lt;/a&gt; and should be able to run it locally.&lt;/p&gt;
&lt;h2&gt;
  
  
  Contributions and Feedback
&lt;/h2&gt;

&lt;p&gt;I've been tinkering with the API design for a while, and it's definitely not stable yet. I would love to get feedback on the current state as well as suggestions for changes or improvements. If you are eager to contribute to this project, please go ahead and submit feature requests or pull requests.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/zirkelc" rel="noopener noreferrer"&gt;
        zirkelc
      &lt;/a&gt; / &lt;a href="https://github.com/zirkelc/middy-store" rel="noopener noreferrer"&gt;
        middy-store
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Middleware for Step Functions: Automatically Store and Load Payloads
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a href="https://github.com/zirkelc/middy-store/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/zirkelc/middy-store/actions/workflows/ci.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;
&lt;a href="https://www.npmjs.com/package/middy-store" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/3f118e66912538d25edc737e811cd42c870d633ae07634cf09e4e2482f12a903/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f6d696464792d73746f7265" alt="npm"&gt;&lt;/a&gt;
&lt;a href="https://www.npmjs.com/package/middy-store" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/41bfaf5544183ea49c671918eb2105cd0e2248eb3f95fdaa8e43717fee3bb9f8/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f64742f6d696464792d73746f7265" alt="npm"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Middleware &lt;code&gt;middy-store&lt;/code&gt;
&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;code&gt;middy-store&lt;/code&gt; is a middleware for Lambda that automatically stores and loads payloads from and to a Store like Amazon S3 or potentially other services.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;You will need &lt;a href="https://www.npmjs.com/package/@middy/core" rel="nofollow noopener noreferrer"&gt;@middy/core&lt;/a&gt; &amp;gt;= v5 to use &lt;code&gt;middy-store&lt;/code&gt;
Please be aware that the API is not stable yet and might change in the future. To avoid accidental breaking changes, please pin the version of &lt;code&gt;middy-store&lt;/code&gt; and its sub-packages in your &lt;code&gt;package.json&lt;/code&gt; to an exact version.&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install --save-exact @middy/core middy-store middy-store-s3 &lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Motivation&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;AWS services have certain limits that one must be aware of. For example, AWS Lambda has a &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html" rel="nofollow noopener noreferrer"&gt;payload limit&lt;/a&gt; of 6MB for synchronous invocations and 256KB for asynchronous invocations. AWS Step Functions allows for a &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/service-quotas.html#service-limits-state-machine-executions" rel="nofollow noopener noreferrer"&gt;maximum input or output size&lt;/a&gt; of 256KB of data as a UTF-8 encoded string. If you exceed this limit when returning data, you will encounter the infamous &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/concepts-error-handling.html#error-data-limit-exceed" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;States.DataLimitExceeded&lt;/code&gt;&lt;/a&gt; exception.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/96862e07de6d29907d66735a1b110ee4f36b923233f0199fa6823b23db3f7308/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f71717964766866687a68756633686135653479332e706e67"&gt;&lt;img src="https://camo.githubusercontent.com/96862e07de6d29907d66735a1b110ee4f36b923233f0199fa6823b23db3f7308/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f71717964766866687a68756633686135653479332e706e67" alt="States.DataLimitExceeded"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The usual workaround for this…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/zirkelc/middy-store" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>programming</category>
    </item>
    <item>
      <title>What's a Valuable Skill You Should Have?</title>
      <dc:creator>Chris Cook</dc:creator>
      <pubDate>Tue, 30 Jul 2024 04:00:00 +0000</pubDate>
      <link>https://forem.com/zirkelc/whats-a-valuable-skill-you-should-have-2igi</link>
      <guid>https://forem.com/zirkelc/whats-a-valuable-skill-you-should-have-2igi</guid>
      <description>&lt;p&gt;There are certain skills that I always admire in other people. For example, when someone is fluent in using the command line, knows the right commands, and combines them by piping the output from one as input to another.&lt;/p&gt;

&lt;p&gt;However, one valuable skill that I think everyone should have - programmer or not - is the ability to read and write &lt;em&gt;Regular Expressions&lt;/em&gt;. You don't have to be perfect at it, but you just need to know that they exist, understand their basic structure, and grasp what the different symbols mean.&lt;/p&gt;

&lt;p&gt;Regular expressions come in handy in many situations, be it refactoring a codebase with search and replace, searching through log files, or working with spreadsheets.&lt;/p&gt;

&lt;p&gt;What do you think is a valuable skill for someone to have?&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>watercooler</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Collect Logs and Metrics from non-AWS Server using CloudWatch Agent</title>
      <dc:creator>Chris Cook</dc:creator>
      <pubDate>Mon, 29 Jul 2024 07:51:34 +0000</pubDate>
      <link>https://forem.com/aws-builders/collect-logs-and-metrics-from-non-aws-server-using-cloudwatch-agent-1lcf</link>
      <guid>https://forem.com/aws-builders/collect-logs-and-metrics-from-non-aws-server-using-cloudwatch-agent-1lcf</guid>
      <description>&lt;p&gt;Most AWS services provide logs or metrics to CloudWatch, which you can use to monitor your system's health or identify errors. However, services outside of AWS don't inherently benefit from these features. Less well known (judging by the number of GitHub stars) is that AWS offers CloudWatch Agent, an OS service to collect and export system metrics and logs from on-premise servers running outside of AWS.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/aws" rel="noopener noreferrer"&gt;
        aws
      &lt;/a&gt; / &lt;a href="https://github.com/aws/amazon-cloudwatch-agent" rel="noopener noreferrer"&gt;
        amazon-cloudwatch-agent
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      CloudWatch Agent enables you to collect and export host-level metrics and logs on instances running Linux or Windows server.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Amazon CloudWatch Agent&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;The Amazon CloudWatch Agent is software developed for the &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Install-CloudWatch-Agent.html" rel="nofollow noopener noreferrer"&gt;CloudWatch Agent&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Overview&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;The Amazon CloudWatch Agent enables you to do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Collect more system-level metrics from Amazon EC2 instances across operating systems. The metrics can include in-guest metrics, in addition to the metrics for EC2 instances. The additional metrics that can be collected are listed in &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/metrics-collected-by-CloudWatch-agent.html" rel="nofollow noopener noreferrer"&gt;Metrics Collected by the CloudWatch Agent&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Collect system-level metrics from on-premises servers. These can include servers in a hybrid environment as well as servers not managed by AWS.&lt;/li&gt;
&lt;li&gt;Retrieve custom metrics from your applications or services using the StatsD and collectd protocols. StatsD is supported on both Linux servers and servers running Windows Server. collectd is supported only on Linux servers.&lt;/li&gt;
&lt;li&gt;Collect logs from Amazon EC2 instances and on-premises servers, running either Linux or Windows Server.&lt;/li&gt;
&lt;li&gt;Collect Open Telemetry and AWS X-Ray traces&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Amazon CloudWatch Agent uses open-source projects…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/aws/amazon-cloudwatch-agent" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;In this short post, I've compiled the most important information to help you quickly set up CloudWatch Agent on a server. All information comes from the official documentation, which I'll link for reference. If you prefer a comprehensive overview before starting, I recommend visiting the &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Install-CloudWatch-Agent.html" rel="noopener noreferrer"&gt;official docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create IAM User
&lt;/h2&gt;

&lt;p&gt;If your server isn't already using AWS and doesn't have its own user, you'll need to create one. CloudWatch Agent requires permissions to send logs and metrics to CloudWatch. Follow these &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/create-iam-roles-for-cloudwatch-agent-commandline.html" rel="noopener noreferrer"&gt;steps&lt;/a&gt; to create a new user. Then, assign one of the managed policies provided by AWS:&lt;/p&gt;

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

&lt;p&gt;I'm using the &lt;code&gt;CloudWatchAgentAdminPolicy&lt;/code&gt;, which is almost identical to the &lt;code&gt;CloudWatchAgentServerPolicy&lt;/code&gt;. The main difference is that the &lt;code&gt;CloudWatchAgentAdminPolicy&lt;/code&gt; allows creating and updating SSM Parameters, while the &lt;code&gt;CloudWatchAgentServerPolicy&lt;/code&gt; can only read from SSM. CloudWatch Agent can store and retrieve its config from SSM. When we later initialize CloudWatch Agent, we can directly push the config to SSM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure AWS CLI
&lt;/h2&gt;

&lt;p&gt;If you haven't installed the AWS CLI on your server, follow the instructions in the &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;install guide&lt;/a&gt;. CloudWatch Agent uses the profile &lt;code&gt;AmazonCloudWatchAgent&lt;/code&gt; for the AWS CLI, so we must configure it with the credentials.&lt;/p&gt;

&lt;p&gt;Run the following command and enter the credentials from the previous step:&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="nb"&gt;sudo &lt;/span&gt;aws configure &lt;span class="nt"&gt;--profile&lt;/span&gt; AmazonCloudWatchAgent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've configured the AWS CLI previously and can't remember the credentials for the IAM user, you can retrieve them by running &lt;code&gt;aws configure get aws_access_key_id&lt;/code&gt; and &lt;code&gt;aws configure get aws_secret_access_key&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install CloudWatch Agent
&lt;/h2&gt;

&lt;p&gt;CloudWatch Agent is available for multiple operating systems and architectures. You can find the appropriate download link for your system in this &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/download-cloudwatch-agent-commandline.html" rel="noopener noreferrer"&gt;table&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Amazon provides RPM and DEB packages for Linux. My server runs on Ubuntu, so I'll use the DEB package. It can be downloaded using &lt;code&gt;wget&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://amazoncloudwatch-agent.s3.amazonaws.com/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After downloading, change to the directory and start the installation. The command depends on 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;&lt;span class="c"&gt;# DEB package&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;dpkg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; ./amazon-cloudwatch-agent.deb

&lt;span class="c"&gt;# RPM package&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;rpm &lt;span class="nt"&gt;-U&lt;/span&gt; ./amazon-cloudwatch-agent.rpm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create Config with Wizard
&lt;/h2&gt;

&lt;p&gt;Before running CloudWatch Agent on your server, you must create a configuration file. The configuration specifies the metrics and logs that the Agent will collect from the server. You can find the supported metrics in this &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/metrics-collected-by-CloudWatch-agent.html" rel="noopener noreferrer"&gt;table&lt;/a&gt;. The collected logs are specified as file paths like &lt;code&gt;/var/log/auth.log&lt;/code&gt;, &lt;code&gt;/var/log/auth.log*&lt;/code&gt;, or even &lt;code&gt;/var/log/**.log&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The wizard will take you through a few questions to create a configuration. If you are uncertain about a question, just go with the default choice. You can always start the wizard again or manually edit the config afterward. If you want to store the config as a parameter in SSM, make sure to answer the question with yes and that your IAM user has the &lt;code&gt;CloudWatchAgentAdminPolicy&lt;/code&gt; policy.&lt;/p&gt;

&lt;p&gt;There's one question regarding running the Agent as the root user or as a different user. The &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Agent-common-scenarios.html#CloudWatch-Agent-run-as-user" rel="noopener noreferrer"&gt;docs&lt;/a&gt; mention that CloudWatch Agent runs as the root user by default, but the default choice in the wizard was to run it as user &lt;code&gt;cwagent.&lt;/code&gt; If you decide to run it as a different user, be sure to create this user and configure the AWS CLI for it. I overwrote the default choice and run it as the &lt;code&gt;root&lt;/code&gt; user.&lt;/p&gt;

&lt;p&gt;To start the wizard, run this command:&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="nb"&gt;sudo&lt;/span&gt; /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-config-wizard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;
  If you want to double-check your answers, you can see my answers here.
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-config-wizard
&lt;span class="o"&gt;================================================================&lt;/span&gt;
&lt;span class="o"&gt;=&lt;/span&gt; Welcome to the Amazon CloudWatch Agent Configuration Manager &lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="o"&gt;=&lt;/span&gt;                                                              &lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="o"&gt;=&lt;/span&gt; CloudWatch Agent allows you to collect metrics and logs from &lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="o"&gt;=&lt;/span&gt; your host and send them to CloudWatch. Additional CloudWatch &lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="o"&gt;=&lt;/span&gt; charges may apply.                                           &lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="o"&gt;================================================================&lt;/span&gt;
On which OS are you planning to use the agent?
1. linux
2. windows
3. darwin
default choice: &lt;span class="o"&gt;[&lt;/span&gt;1]:

Trying to fetch the default region based on ec2 metadata...
Are you using EC2 or On-Premises hosts?
1. EC2
2. On-Premises
default choice: &lt;span class="o"&gt;[&lt;/span&gt;2]:

Please make sure the credentials and region &lt;span class="nb"&gt;set &lt;/span&gt;correctly on your hosts.
Refer to http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html
Which user are you planning to run the agent?
1. cwagent
2. root
3. others
default choice: &lt;span class="o"&gt;[&lt;/span&gt;1]:
2
Do you want to turn on StatsD daemon?
1. &lt;span class="nb"&gt;yes
&lt;/span&gt;2. no
default choice: &lt;span class="o"&gt;[&lt;/span&gt;1]:

Which port &lt;span class="k"&gt;do &lt;/span&gt;you want StatsD daemon to listen to?
default choice: &lt;span class="o"&gt;[&lt;/span&gt;8125]

What is the collect interval &lt;span class="k"&gt;for &lt;/span&gt;StatsD daemon?
1. 10s
2. 30s
3. 60s
default choice: &lt;span class="o"&gt;[&lt;/span&gt;1]:
3
What is the aggregation interval &lt;span class="k"&gt;for &lt;/span&gt;metrics collected by StatsD daemon?
1. Do not aggregate
2. 10s
3. 30s
4. 60s
default choice: &lt;span class="o"&gt;[&lt;/span&gt;4]:

Do you want to monitor metrics from CollectD? WARNING: CollectD must be installed or the Agent will fail to start
1. &lt;span class="nb"&gt;yes
&lt;/span&gt;2. no
default choice: &lt;span class="o"&gt;[&lt;/span&gt;1]:

Do you want to monitor any host metrics? e.g. CPU, memory, etc.
1. &lt;span class="nb"&gt;yes
&lt;/span&gt;2. no
default choice: &lt;span class="o"&gt;[&lt;/span&gt;1]:

Do you want to monitor cpu metrics per core?
1. &lt;span class="nb"&gt;yes
&lt;/span&gt;2. no
default choice: &lt;span class="o"&gt;[&lt;/span&gt;1]:
2
Would you like to collect your metrics at high resolution &lt;span class="o"&gt;(&lt;/span&gt;sub-minute resolution&lt;span class="o"&gt;)&lt;/span&gt;? This enables sub-minute resolution &lt;span class="k"&gt;for &lt;/span&gt;all metrics, but you can customize &lt;span class="k"&gt;for &lt;/span&gt;specific metrics &lt;span class="k"&gt;in &lt;/span&gt;the output json file.
1. 1s
2. 10s
3. 30s
4. 60s
default choice: &lt;span class="o"&gt;[&lt;/span&gt;4]:

Which default metrics config &lt;span class="k"&gt;do &lt;/span&gt;you want?
1. Basic
2. Standard
3. Advanced
4. None
default choice: &lt;span class="o"&gt;[&lt;/span&gt;1]:

Current config as follows:
&lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"agent"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;"metrics_collection_interval"&lt;/span&gt;: 60,
                &lt;span class="s2"&gt;"run_as_user"&lt;/span&gt;: &lt;span class="s2"&gt;"root"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="s2"&gt;"metrics"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;"metrics_collected"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                        &lt;span class="s2"&gt;"collectd"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                                &lt;span class="s2"&gt;"metrics_aggregation_interval"&lt;/span&gt;: 60
                        &lt;span class="o"&gt;}&lt;/span&gt;,
                        &lt;span class="s2"&gt;"cpu"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                                &lt;span class="s2"&gt;"measurement"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                                        &lt;span class="s2"&gt;"cpu_usage_idle"&lt;/span&gt;
                                &lt;span class="o"&gt;]&lt;/span&gt;,
                                &lt;span class="s2"&gt;"metrics_collection_interval"&lt;/span&gt;: 60,
                                &lt;span class="s2"&gt;"totalcpu"&lt;/span&gt;: &lt;span class="nb"&gt;true&lt;/span&gt;
                        &lt;span class="o"&gt;}&lt;/span&gt;,
                        &lt;span class="s2"&gt;"disk"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                                &lt;span class="s2"&gt;"measurement"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                                        &lt;span class="s2"&gt;"used_percent"&lt;/span&gt;
                                &lt;span class="o"&gt;]&lt;/span&gt;,
                                &lt;span class="s2"&gt;"metrics_collection_interval"&lt;/span&gt;: 60,
                                &lt;span class="s2"&gt;"resources"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                                        &lt;span class="s2"&gt;"*"&lt;/span&gt;
                                &lt;span class="o"&gt;]&lt;/span&gt;
                        &lt;span class="o"&gt;}&lt;/span&gt;,
                        &lt;span class="s2"&gt;"diskio"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                                &lt;span class="s2"&gt;"measurement"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                                        &lt;span class="s2"&gt;"write_bytes"&lt;/span&gt;,
                                        &lt;span class="s2"&gt;"read_bytes"&lt;/span&gt;,
                                        &lt;span class="s2"&gt;"writes"&lt;/span&gt;,
                                        &lt;span class="s2"&gt;"reads"&lt;/span&gt;
                                &lt;span class="o"&gt;]&lt;/span&gt;,
                                &lt;span class="s2"&gt;"metrics_collection_interval"&lt;/span&gt;: 60,
                                &lt;span class="s2"&gt;"resources"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                                        &lt;span class="s2"&gt;"*"&lt;/span&gt;
                                &lt;span class="o"&gt;]&lt;/span&gt;
                        &lt;span class="o"&gt;}&lt;/span&gt;,
                        &lt;span class="s2"&gt;"mem"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                                &lt;span class="s2"&gt;"measurement"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                                        &lt;span class="s2"&gt;"mem_used_percent"&lt;/span&gt;
                                &lt;span class="o"&gt;]&lt;/span&gt;,
                                &lt;span class="s2"&gt;"metrics_collection_interval"&lt;/span&gt;: 60
                        &lt;span class="o"&gt;}&lt;/span&gt;,
                        &lt;span class="s2"&gt;"net"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                                &lt;span class="s2"&gt;"measurement"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                                        &lt;span class="s2"&gt;"bytes_sent"&lt;/span&gt;,
                                        &lt;span class="s2"&gt;"bytes_recv"&lt;/span&gt;,
                                        &lt;span class="s2"&gt;"packets_sent"&lt;/span&gt;,
                                        &lt;span class="s2"&gt;"packets_recv"&lt;/span&gt;
                                &lt;span class="o"&gt;]&lt;/span&gt;,
                                &lt;span class="s2"&gt;"metrics_collection_interval"&lt;/span&gt;: 60,
                                &lt;span class="s2"&gt;"resources"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                                        &lt;span class="s2"&gt;"*"&lt;/span&gt;
                                &lt;span class="o"&gt;]&lt;/span&gt;
                        &lt;span class="o"&gt;}&lt;/span&gt;,
                        &lt;span class="s2"&gt;"statsd"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                                &lt;span class="s2"&gt;"metrics_aggregation_interval"&lt;/span&gt;: 60,
                                &lt;span class="s2"&gt;"metrics_collection_interval"&lt;/span&gt;: 60,
                                &lt;span class="s2"&gt;"service_address"&lt;/span&gt;: &lt;span class="s2"&gt;":8125"&lt;/span&gt;
                        &lt;span class="o"&gt;}&lt;/span&gt;,
                        &lt;span class="s2"&gt;"swap"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                                &lt;span class="s2"&gt;"measurement"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                                        &lt;span class="s2"&gt;"swap_used_percent"&lt;/span&gt;
                                &lt;span class="o"&gt;]&lt;/span&gt;,
                                &lt;span class="s2"&gt;"metrics_collection_interval"&lt;/span&gt;: 60
                        &lt;span class="o"&gt;}&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
Are you satisfied with the above config? Note: it can be manually customized after the wizard completes to add additional items.
1. &lt;span class="nb"&gt;yes
&lt;/span&gt;2. no
default choice: &lt;span class="o"&gt;[&lt;/span&gt;1]:

Do you have any existing CloudWatch Log Agent &lt;span class="o"&gt;(&lt;/span&gt;http://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AgentReference.html&lt;span class="o"&gt;)&lt;/span&gt; configuration file to import &lt;span class="k"&gt;for &lt;/span&gt;migration?
1. &lt;span class="nb"&gt;yes
&lt;/span&gt;2. no
default choice: &lt;span class="o"&gt;[&lt;/span&gt;2]:

Do you want to monitor any log files?
1. &lt;span class="nb"&gt;yes
&lt;/span&gt;2. no
default choice: &lt;span class="o"&gt;[&lt;/span&gt;1]:

Log file path:
/var/log/auth.log
Log group name:
default choice: &lt;span class="o"&gt;[&lt;/span&gt;auth.log]
auth-log
Log group class:
1. STANDARD
2. INFREQUENT_ACCESS
default choice: &lt;span class="o"&gt;[&lt;/span&gt;1]:

Log stream name:
default choice: &lt;span class="o"&gt;[{&lt;/span&gt;&lt;span class="nb"&gt;hostname&lt;/span&gt;&lt;span class="o"&gt;}]&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;hostname&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;      
Log Group Retention &lt;span class="k"&gt;in &lt;/span&gt;days
1. &lt;span class="nt"&gt;-1&lt;/span&gt;
2. 1
3. 3
4. 5
5. 7
6. 14
7. 30
8. 60
9. 90
10. 120
11. 150
12. 180
13. 365
14. 400
15. 545
16. 731
17. 1096
18. 1827
19. 2192
20. 2557
21. 2922
22. 3288
23. 3653
default choice: &lt;span class="o"&gt;[&lt;/span&gt;1]:
7
Do you want to specify any additional log files to monitor?
1. &lt;span class="nb"&gt;yes
&lt;/span&gt;2. no
default choice: &lt;span class="o"&gt;[&lt;/span&gt;1]:
2
Do you want the CloudWatch agent to also retrieve X-ray traces?
1. &lt;span class="nb"&gt;yes
&lt;/span&gt;2. no
default choice: &lt;span class="o"&gt;[&lt;/span&gt;1]:
2
Existing config JSON identified and copied to:  /opt/aws/amazon-cloudwatch-agent/etc/backup-configs
Saved config file to /opt/aws/amazon-cloudwatch-agent/bin/config.json successfully.
Current config as follows:
&lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"agent"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;"metrics_collection_interval"&lt;/span&gt;: 60,
                &lt;span class="s2"&gt;"run_as_user"&lt;/span&gt;: &lt;span class="s2"&gt;"root"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="s2"&gt;"logs"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;"logs_collected"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                        &lt;span class="s2"&gt;"files"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                                &lt;span class="s2"&gt;"collect_list"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                                        &lt;span class="o"&gt;{&lt;/span&gt;
                                                &lt;span class="s2"&gt;"file_path"&lt;/span&gt;: &lt;span class="s2"&gt;"/var/log/auth.log"&lt;/span&gt;,
                                                &lt;span class="s2"&gt;"log_group_class"&lt;/span&gt;: &lt;span class="s2"&gt;"STANDARD"&lt;/span&gt;,
                                                &lt;span class="s2"&gt;"log_group_name"&lt;/span&gt;: &lt;span class="s2"&gt;"auth-log"&lt;/span&gt;,
                                                &lt;span class="s2"&gt;"log_stream_name"&lt;/span&gt;: &lt;span class="s2"&gt;"{hostname}"&lt;/span&gt;,
                                                &lt;span class="s2"&gt;"retention_in_days"&lt;/span&gt;: 30
                                        &lt;span class="o"&gt;}&lt;/span&gt;
                                &lt;span class="o"&gt;]&lt;/span&gt;
                        &lt;span class="o"&gt;}&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="s2"&gt;"metrics"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;"metrics_collected"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                        &lt;span class="s2"&gt;"collectd"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                                &lt;span class="s2"&gt;"metrics_aggregation_interval"&lt;/span&gt;: 60
                        &lt;span class="o"&gt;}&lt;/span&gt;,
                        &lt;span class="s2"&gt;"cpu"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                                &lt;span class="s2"&gt;"measurement"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                                        &lt;span class="s2"&gt;"cpu_usage_idle"&lt;/span&gt;
                                &lt;span class="o"&gt;]&lt;/span&gt;,
                                &lt;span class="s2"&gt;"metrics_collection_interval"&lt;/span&gt;: 60,
                                &lt;span class="s2"&gt;"totalcpu"&lt;/span&gt;: &lt;span class="nb"&gt;true&lt;/span&gt;
                        &lt;span class="o"&gt;}&lt;/span&gt;,
                        &lt;span class="s2"&gt;"disk"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                                &lt;span class="s2"&gt;"measurement"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                                        &lt;span class="s2"&gt;"used_percent"&lt;/span&gt;
                                &lt;span class="o"&gt;]&lt;/span&gt;,
                                &lt;span class="s2"&gt;"metrics_collection_interval"&lt;/span&gt;: 60,
                                &lt;span class="s2"&gt;"resources"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                                        &lt;span class="s2"&gt;"*"&lt;/span&gt;
                                &lt;span class="o"&gt;]&lt;/span&gt;
                        &lt;span class="o"&gt;}&lt;/span&gt;,
                        &lt;span class="s2"&gt;"diskio"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                                &lt;span class="s2"&gt;"measurement"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                                        &lt;span class="s2"&gt;"write_bytes"&lt;/span&gt;,
                                        &lt;span class="s2"&gt;"read_bytes"&lt;/span&gt;,
                                        &lt;span class="s2"&gt;"writes"&lt;/span&gt;,
                                        &lt;span class="s2"&gt;"reads"&lt;/span&gt;
                                &lt;span class="o"&gt;]&lt;/span&gt;,
                                &lt;span class="s2"&gt;"metrics_collection_interval"&lt;/span&gt;: 60,
                                &lt;span class="s2"&gt;"resources"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                                        &lt;span class="s2"&gt;"*"&lt;/span&gt;
                                &lt;span class="o"&gt;]&lt;/span&gt;
                        &lt;span class="o"&gt;}&lt;/span&gt;,
                        &lt;span class="s2"&gt;"mem"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                                &lt;span class="s2"&gt;"measurement"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                                        &lt;span class="s2"&gt;"mem_used_percent"&lt;/span&gt;
                                &lt;span class="o"&gt;]&lt;/span&gt;,
                                &lt;span class="s2"&gt;"metrics_collection_interval"&lt;/span&gt;: 60
                        &lt;span class="o"&gt;}&lt;/span&gt;,
                        &lt;span class="s2"&gt;"net"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                                &lt;span class="s2"&gt;"measurement"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                                        &lt;span class="s2"&gt;"bytes_sent"&lt;/span&gt;,
                                        &lt;span class="s2"&gt;"bytes_recv"&lt;/span&gt;,
                                        &lt;span class="s2"&gt;"packets_sent"&lt;/span&gt;,
                                        &lt;span class="s2"&gt;"packets_recv"&lt;/span&gt;
                                &lt;span class="o"&gt;]&lt;/span&gt;,
                                &lt;span class="s2"&gt;"metrics_collection_interval"&lt;/span&gt;: 60,
                                &lt;span class="s2"&gt;"resources"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                                        &lt;span class="s2"&gt;"*"&lt;/span&gt;
                                &lt;span class="o"&gt;]&lt;/span&gt;
                        &lt;span class="o"&gt;}&lt;/span&gt;,
                        &lt;span class="s2"&gt;"statsd"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                                &lt;span class="s2"&gt;"metrics_aggregation_interval"&lt;/span&gt;: 60,
                                &lt;span class="s2"&gt;"metrics_collection_interval"&lt;/span&gt;: 60,
                                &lt;span class="s2"&gt;"service_address"&lt;/span&gt;: &lt;span class="s2"&gt;":8125"&lt;/span&gt;
                        &lt;span class="o"&gt;}&lt;/span&gt;,
                        &lt;span class="s2"&gt;"swap"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                                &lt;span class="s2"&gt;"measurement"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                                        &lt;span class="s2"&gt;"swap_used_percent"&lt;/span&gt;
                                &lt;span class="o"&gt;]&lt;/span&gt;,
                                &lt;span class="s2"&gt;"metrics_collection_interval"&lt;/span&gt;: 60
                        &lt;span class="o"&gt;}&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
Please check the above content of the config.
The config file is also located at /opt/aws/amazon-cloudwatch-agent/bin/config.json.
Edit it manually &lt;span class="k"&gt;if &lt;/span&gt;needed.
Do you want to store the config &lt;span class="k"&gt;in &lt;/span&gt;the SSM parameter store?
1. &lt;span class="nb"&gt;yes
&lt;/span&gt;2. no
default choice: &lt;span class="o"&gt;[&lt;/span&gt;1]:

What parameter store name &lt;span class="k"&gt;do &lt;/span&gt;you want to use to store your config? &lt;span class="o"&gt;(&lt;/span&gt;Use &lt;span class="s1"&gt;'AmazonCloudWatch-'&lt;/span&gt; prefix &lt;span class="k"&gt;if &lt;/span&gt;you use our managed AWS policy&lt;span class="o"&gt;)&lt;/span&gt;
default choice: &lt;span class="o"&gt;[&lt;/span&gt;AmazonCloudWatch-linux]
AmazonCloudWatch-agent-config
Which region &lt;span class="k"&gt;do &lt;/span&gt;you want to store the config &lt;span class="k"&gt;in &lt;/span&gt;the parameter store?
default choice: &lt;span class="o"&gt;[&lt;/span&gt;eu-west-1]

Which AWS credential should be used to send json config to parameter store?
1. XXXXXX... &lt;span class="o"&gt;(&lt;/span&gt;From SDK&lt;span class="o"&gt;)&lt;/span&gt;
2. Other
default choice: &lt;span class="o"&gt;[&lt;/span&gt;1]:

Successfully put config to parameter store AmazonCloudWatch-agent-config.
Program exits now.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure credentials
&lt;/h2&gt;

&lt;p&gt;When you start the CloudWatch Agent, it will run as a &lt;code&gt;systemd&lt;/code&gt; service. That means, environment variables such as &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt; are not accessible.&lt;br&gt;
By default, CloudWatch Agent will search for the credentials in &lt;code&gt;/root/.aws&lt;/code&gt; on Linux. Depending on how you installed the AWS CLI, the credentials might be located somewhere else, e.g. usually in the users' &lt;code&gt;/home/&amp;lt;user&amp;gt;/.aws&lt;/code&gt; directory. In this case, you need to specify the location of the credentials file in &lt;code&gt;opt/aws/amazon-cloudwatch-agent/etc/common-config.toml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;## Configuration for shared credential.&lt;/span&gt;
&lt;span class="c"&gt;## Default credential strategy will be used if it is absent here:&lt;/span&gt;
&lt;span class="c"&gt;##            Instance role is used for EC2 case by default.&lt;/span&gt;
&lt;span class="c"&gt;##            AmazonCloudWatchAgent profile is used for the on-premises case by default.&lt;/span&gt;

&lt;span class="nn"&gt;[credentials]&lt;/span&gt;
&lt;span class="c"&gt;#  shared_credential_profile = "{profile_name}"&lt;/span&gt;
   &lt;span class="py"&gt;shared_credential_file&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/home/&amp;lt;user&amp;gt;/.aws/credentials"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Agent-Credentials-Preference.html" rel="noopener noreferrer"&gt;CloudWatch agent credentials preference&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/install-CloudWatch-Agent-commandline-fleet.html#CloudWatch-Agent-profile-instance-first" rel="noopener noreferrer"&gt;Modify the common configuration for proxy or Region information&lt;/a&gt; for more information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing &lt;code&gt;CollectD&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;CloudWatch Agent uses the &lt;a href="https://github.com/collectd/collectd" rel="noopener noreferrer"&gt;&lt;code&gt;CollectD&lt;/code&gt;&lt;/a&gt; service to collect metrics. If &lt;code&gt;CollectD&lt;/code&gt; is not installed on your system, the Agent will fail to start. If you are not sure if it's installed, here is how you can check if &lt;code&gt;CollectD&lt;/code&gt; is installed and active:&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="nb"&gt;sudo &lt;/span&gt;systemctl status collectd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will show you if the service is active, inactive, or if there are any issues with it. Alternatively, you can also look for &lt;code&gt;CollectD&lt;/code&gt; in the running processes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ps aux | &lt;span class="nb"&gt;grep &lt;/span&gt;collectd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;CollectD&lt;/code&gt; is not installed, you can install it from your package manager. On Ubuntu, you can install it using &lt;code&gt;apt&lt;/code&gt;:&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="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;collectd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, start and enable the &lt;code&gt;CollectD&lt;/code&gt; service:&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="nb"&gt;sudo &lt;/span&gt;systemctl start collectd
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;collectd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, verify if &lt;code&gt;CollectD&lt;/code&gt; is active and running:&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="nb"&gt;sudo &lt;/span&gt;systemctl status collectd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Start CloudWatch Agent
&lt;/h2&gt;

&lt;p&gt;The Agent can be controlled by the script located at &lt;code&gt;/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl&lt;/code&gt;. If you run it without any arguments, it will show the help:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl

        usage:  amazon-cloudwatch-agent-ctl &lt;span class="nt"&gt;-a&lt;/span&gt;
                stop|start|status|fetch-config|append-config|remove-config|set-log-level
                &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt; ec2|onPremise|onPrem|auto]
                &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt; default|all|ssm:&amp;lt;parameter-store-name&amp;gt;|file:&amp;lt;file-path&amp;gt;]
                &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-s&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
                &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-l&lt;/span&gt; INFO|DEBUG|WARN|ERROR|OFF]

        e.g.
        1. apply a SSM parameter store config on EC2 instance and restart the agent afterwards:
            amazon-cloudwatch-agent-ctl &lt;span class="nt"&gt;-a&lt;/span&gt; fetch-config &lt;span class="nt"&gt;-m&lt;/span&gt; ec2 &lt;span class="nt"&gt;-c&lt;/span&gt; ssm:AmazonCloudWatch-Config.json &lt;span class="nt"&gt;-s&lt;/span&gt;
        2. append a &lt;span class="nb"&gt;local &lt;/span&gt;json config file on onPremise host and restart the agent afterwards:
            amazon-cloudwatch-agent-ctl &lt;span class="nt"&gt;-a&lt;/span&gt; append-config &lt;span class="nt"&gt;-m&lt;/span&gt; onPremise &lt;span class="nt"&gt;-c&lt;/span&gt; file:/tmp/config.json &lt;span class="nt"&gt;-s&lt;/span&gt;
        3. query agent status:
            amazon-cloudwatch-agent-ctl &lt;span class="nt"&gt;-a&lt;/span&gt; status

        &lt;span class="nt"&gt;-a&lt;/span&gt;: action
            stop:                                   stop the agent process.
            start:                                  start the agent process.
            status:                                 get the status of the agent process.
            fetch-config:                           apply config &lt;span class="k"&gt;for &lt;/span&gt;agent, followed by &lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; Target config can be based on location &lt;span class="o"&gt;(&lt;/span&gt;ssm parameter store name, file name&lt;span class="o"&gt;)&lt;/span&gt;, or &lt;span class="s1"&gt;'default'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
            append-config:                          append json config with the existing json configs &lt;span class="k"&gt;if &lt;/span&gt;any, followed by &lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; Target config can be based on the location &lt;span class="o"&gt;(&lt;/span&gt;ssm parameter store name, file name&lt;span class="o"&gt;)&lt;/span&gt;, or &lt;span class="s1"&gt;'default'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
            remove-config:                          remove config &lt;span class="k"&gt;for &lt;/span&gt;agent, followed by &lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; Target config can be based on the location &lt;span class="o"&gt;(&lt;/span&gt;ssm parameter store name, file name&lt;span class="o"&gt;)&lt;/span&gt;, or &lt;span class="s1"&gt;'all'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
            set-log-level:                          sets the log level, followed by &lt;span class="nt"&gt;-l&lt;/span&gt; to provide the level &lt;span class="k"&gt;in &lt;/span&gt;all caps.

        &lt;span class="nt"&gt;-m&lt;/span&gt;: mode
            ec2:                                    indicate this is on ec2 host.
            onPremise, onPrem:                      indicate this is on onPremise host.
            auto:                                   use ec2 metadata to determine the environment, may not be accurate &lt;span class="k"&gt;if &lt;/span&gt;ec2 metadata is not available &lt;span class="k"&gt;for &lt;/span&gt;some reason on EC2.

        &lt;span class="nt"&gt;-c&lt;/span&gt;: amazon-cloudwatch-agent configuration
            default:                                default configuration &lt;span class="k"&gt;for &lt;/span&gt;quick trial.
            ssm:&amp;lt;parameter-store-name&amp;gt;:             ssm parameter store name.
            file:&amp;lt;file-path&amp;gt;:                       file path on the host.
            all:                                    all existing configs. Only apply to remove-config action.

        &lt;span class="nt"&gt;-s&lt;/span&gt;: optionally restart after configuring the agent configuration
            this parameter is used &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="s1"&gt;'fetch-config'&lt;/span&gt;, &lt;span class="s1"&gt;'append-config'&lt;/span&gt;, &lt;span class="s1"&gt;'remove-config'&lt;/span&gt; action only.

        &lt;span class="nt"&gt;-l&lt;/span&gt;: log level to &lt;span class="nb"&gt;set &lt;/span&gt;the agent to INFO, DEBUG, WARN, ERROR, or OFF
            this parameter is used &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="s1"&gt;'set-log-level'&lt;/span&gt; only.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CloudWatch Agent must be started with the config created from the wizard. If the config was saved to SSM, use &lt;code&gt;-c ssm:configuration-parameter-store-name&lt;/code&gt; with the name of the parameter (not the ARN). Otherwise, use &lt;code&gt;-c file:configuration-file-path&lt;/code&gt; with the path to the config file. To start CloudWatch Agent, run the following command:&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="nb"&gt;sudo&lt;/span&gt; /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl &lt;span class="nt"&gt;-a&lt;/span&gt; fetch-config &lt;span class="nt"&gt;-m&lt;/span&gt; onPremise &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;ssm:configuration-parameter-store-name | file:configuration-file-path]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CloudWatch Agent will fetch the config and validate all settings. If everything is fine, it will enable itself as a system service. You can then verify if the Agent is running with this command:&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="nb"&gt;sudo&lt;/span&gt; /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl &lt;span class="nt"&gt;-m&lt;/span&gt; onPremise &lt;span class="nt"&gt;-a&lt;/span&gt; status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Update Config and Restart
&lt;/h2&gt;

&lt;p&gt;If you need to change the config, you can update the SSM parameter or edit the local config file. You can then restart the Agent with the same command as before:&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="nb"&gt;sudo&lt;/span&gt; /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl &lt;span class="nt"&gt;-a&lt;/span&gt; fetch-config &lt;span class="nt"&gt;-m&lt;/span&gt; onPremise &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;ssm:configuration-parameter-store-name | file:configuration-file-path]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-s&lt;/code&gt; option tells CloudWatch Agent to restart itself after the new config is fetched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;If the Agent shows an error regarding the credentials or region, check the &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/install-CloudWatch-Agent-commandline-fleet.html#CloudWatch-Agent-profile-instance-first" rel="noopener noreferrer"&gt;common configuration&lt;/a&gt; for a valid credentials file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano /opt/aws/amazon-cloudwatch-agent/etc/common-config.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If CloudWatch Agent is not working as expected, take a look at the log files in &lt;code&gt;/opt/aws/amazon-cloudwatch-agent/logs&lt;/code&gt;:&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="nb"&gt;ls&lt;/span&gt; /opt/aws/amazon-cloudwatch-agent/logs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the Agent doesn't even start, check the &lt;code&gt;configuration-validation.log&lt;/code&gt; file to see if there's an issue with the configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano /opt/aws/amazon-cloudwatch-agent/logs/configuration-validation.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the Agent is running but the logs and metrics don't show up in CloudWatch, check the &lt;code&gt;amazon-cloudwatch-agent.log&lt;/code&gt; file for errors (&lt;code&gt;E!&lt;/code&gt;) and warnings (&lt;code&gt;W!&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano /opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Further information can be found in the &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/troubleshooting-CloudWatch-Agent.html" rel="noopener noreferrer"&gt;troubleshooting&lt;/a&gt; section in the docs.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
