<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Armando</title>
    <description>The latest articles on DEV Community by Armando (@armando284).</description>
    <link>https://dev.to/armando284</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F914305%2Fd2bee39a-701f-4a80-9005-aae027ac1ffb.jpeg</url>
      <title>DEV Community: Armando</title>
      <link>https://dev.to/armando284</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/armando284"/>
    <language>en</language>
    <item>
      <title>Sharing a Whiteboard at 120 kbps with an Unstable Connection</title>
      <dc:creator>Armando</dc:creator>
      <pubDate>Mon, 14 Sep 2026 23:57:34 +0000</pubDate>
      <link>https://dev.to/armando284/sharing-a-whiteboard-at-120-kbps-with-an-unstable-connection-5787</link>
      <guid>https://dev.to/armando284/sharing-a-whiteboard-at-120-kbps-with-an-unstable-connection-5787</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I set out to compress a shared whiteboard for a connection that's slow (~120 kbps practical) and drops without warning. The real problem wasn't compression. It was that I was sending information the other client already had. A byte ledger I built before adding audio is now producing numbers: shared audio sits around 40 kbps, leaving about 80 kbps for the whiteboard. The project turned into a lab (LOW-NET) around a bigger question: how little information does a shared experience need? Below: the progression, what's built, what's explicitly &lt;em&gt;not&lt;/em&gt; measured yet, and the open questions.&lt;/p&gt;

&lt;p&gt;Quick honesty note up front: this is the experiment I'm building, not a finished benchmark. Where numbers don't exist yet, I say so.&lt;/p&gt;

&lt;h2&gt;
  
  
  The constraint that started it
&lt;/h2&gt;

&lt;p&gt;I work from Cuba. The practical working connection is around 120 kbps and it disappears and reappears at random: batch service, shared capacity, anything but a stable channel.&lt;/p&gt;

&lt;p&gt;During an online English class, audio worked and live drawing didn't. A teacher can't show you how to write a phrase on a frozen screen.&lt;/p&gt;

&lt;p&gt;That's the design environment. I design against an environment where roughly 120 kbps is a realistic working constraint and connectivity can be intermittent. The target comes from the environment itself, not from capping a link to generate a number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrong model #1: compress the full state
&lt;/h2&gt;

&lt;p&gt;First instinct, and a clean failure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// "aaaaaaabbbb" -&amp;gt; [7, 'a', 4, 'b']&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;rle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seq&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;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;seq&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="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;out&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;j&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;j&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;out&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 black-and-white canvas is a grid of bits; run-length encoding eats the repetitions; done. Correct mechanics, wrong abstraction. Two people drawing don't need the drawing. They need what changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pivot: represent changes, not states
&lt;/h2&gt;

&lt;p&gt;The structural change:&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="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="nx"&gt;shape&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;        &lt;span class="c1"&gt;// a stroke is born&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;     &lt;span class="c1"&gt;// a point moves&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;remove&lt;/span&gt;&lt;span class="dl"&gt;'&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="c1"&gt;// a stroke is erased&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Each client already holds the canvas; you send operations against it, not snapshots.&lt;/li&gt;
&lt;li&gt;This is the "do the work once, on the side that owns the state" move, and for a whiteboard it's the difference between the operation graph and the framebuffer.&lt;/li&gt;
&lt;li&gt;Old idea I re-derived the hard way: &lt;a href="https://www.rfc-editor.org/rfc/rfc6143" rel="noopener noreferrer"&gt;VNC (RFC 6143)&lt;/a&gt; refreshes only dirty screen regions. Same principle, different unit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Serialization: binary + gzip, opportunistically
&lt;/h2&gt;

&lt;p&gt;For the message layer I've been working with a custom binary encoding, gzip-compressed when it pays off (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer" rel="noopener noreferrer"&gt;ArrayBuffer&lt;/a&gt; as the wire unit instead of a huge JSON). Two honest caveats:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Compression has a fixed cost. Below some payload size, a compressor can inflate your data stack (headers, nothing to exploit). On a ~120 kbps channel that threshold matters, and it needs measuring, not guessing.&lt;/li&gt;
&lt;li&gt;"Binary + gzip wins over JSON" is still a working assumption here. The ledger I have measures whole-channel traffic, not per-representation comparisons, so I can't claim a crossover yet.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you've got real thresholds for tiny payloads, I want them (see the end).&lt;/p&gt;

&lt;h2&gt;
  
  
  The byte ledger, and the first real number
&lt;/h2&gt;

&lt;p&gt;Budget only means something if you can see it. Before adding audio I built a small system that registers the data being consumed and sent and lets me view it, so I could actually know whether I was inside the budget. 📊&lt;/p&gt;

&lt;p&gt;Then I added shared audio, and it works. With a few participants, the audio channel registers around 40 kbps, leaving about 80 kbps for the whiteboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;120 kbps  total budget
-40 kbps  shared audio
 80 kbps  whiteboard (and everything that comes after)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the first number with a name. It came from the ledger, not from a guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Audio, or: the part of the problem compression doesn't touch
&lt;/h2&gt;

&lt;p&gt;Voice is what made the class human. The context worth knowing: codec design has been doing the hard version of this for decades. Opus is built for VoIP over constrained links. Its &lt;a href="https://www.rfc-editor.org/rfc/rfc6716" rel="noopener noreferrer"&gt;specification (RFC 6716)&lt;/a&gt; lands in the order of 6 to 510 kbps, and it ships with two ideas I keep coming back to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Discontinuous Transmission (DTX):&lt;/strong&gt; stop transmitting while nobody speaks (&lt;a href="https://opus-codec.org/" rel="noopener noreferrer"&gt;opus-codec.org&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-band FEC:&lt;/strong&gt; spend extra bits protecting against loss when the channel is lossy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are "decide what &lt;em&gt;not&lt;/em&gt; to send," which is the actual game. Compression is one corner of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Video, or: the delta of a face is enormous
&lt;/h2&gt;

&lt;p&gt;Pixels are expensive. Video changes constantly even when nothing meaningful changes, and frames repeat whether the eye notices or not. &lt;em&gt;Send only deltas&lt;/em&gt; applies, but a face generates huge deltas: lips, eyes, expression, every frame.&lt;/p&gt;

&lt;p&gt;The uncomfortable question: &lt;strong&gt;what part of the video is information the other person needs?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two tracks I'm exploring in parallel:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Very low-resolution video:&lt;/strong&gt; still pixel-based, still costly. For ideas I'm borrowing from &lt;a href="https://www.youtube.com/@ThePrimeagen" rel="noopener noreferrer"&gt;ThePrimeagen's&lt;/a&gt; ASCII art, particle systems and ASCII Doom videos, and I'm even toying with something shader-like that would carry information through those ASCII algorithms. 🕹️&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avatar / semantic representation:&lt;/strong&gt; transmit body state instead of pixels:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;head&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rot&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;hands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...],&lt;/span&gt; &lt;span class="nx"&gt;posture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;seated&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;Same whiteboard intuition: find the minimum the other side needs, send that. The avatar currently works, although sending only the minimum makes it hard to carry the gestures of a human face in a congruent way. I don't yet know where it stops feeling human. That's a measurement on the list.&lt;/p&gt;

&lt;h2&gt;
  
  
  The assumption I had to unlearn: continuity
&lt;/h2&gt;

&lt;p&gt;The bigger lesson wasn't bandwidth. A channel that does this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;available → degraded → unavailable → available → burst → unavailable → ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;behaves nothing like "120 kbps sustained." When there's no channel, compression does nothing. The question becomes &lt;strong&gt;what to do while you're disconnected&lt;/strong&gt;: retain, prioritize, batch, and push when it returns.&lt;/p&gt;

&lt;p&gt;My hypothesis is that this is a shared-capacity problem rather than a speed one: an antenna serving many devices in batches. I haven't verified it, and I offer it as what it is, an informal observation my model happens to fit, not a measurement. ⚠️&lt;/p&gt;

&lt;h2&gt;
  
  
  Related work I found: DTN
&lt;/h2&gt;

&lt;p&gt;Researching "designing for a channel that can be absent" led me to &lt;strong&gt;Delay/Disruption Tolerant Networking&lt;/strong&gt;. NASA's definition: an architecture for networks with disruptions, delays, and data-rate mismatches. Core mechanism: &lt;strong&gt;store-and-forward&lt;/strong&gt;, hold the message, keep trying until there's a path.&lt;/p&gt;

&lt;p&gt;This isn't something I invented: &lt;a href="https://www.rfc-editor.org/rfc/rfc4838" rel="noopener noreferrer"&gt;RFC 4838&lt;/a&gt; (architecture) and &lt;a href="https://www.rfc-editor.org/rfc/rfc9171" rel="noopener noreferrer"&gt;RFC 9171&lt;/a&gt; (Bundle Protocol v7) are public, and the field has decades of documented work.&lt;/p&gt;

&lt;p&gt;Framing matters: my problem is &lt;strong&gt;not&lt;/strong&gt; deep space 😅. LOW-NET is a handful of participants and a shared board, with delays of seconds rather than minutes. Implementing full Bundle Protocol v7 here would be over-engineering. The move was ingesting the ideas: &lt;em&gt;no continuity&lt;/em&gt; as a first-class condition, not the protocol.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I explicitly do NOT have yet
&lt;/h2&gt;

&lt;p&gt;The numbers that would make this a benchmark post do not exist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no measured savings for full-state vs deltas;&lt;/li&gt;
&lt;li&gt;no measured binary-vs-JSON comparison on this codebase;&lt;/li&gt;
&lt;li&gt;no measured video/avatar quality-vs-bitrate curve;&lt;/li&gt;
&lt;li&gt;no reconnect/reconcile timing;&lt;/li&gt;
&lt;li&gt;no compression-threshold measurements on real payloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What I do have so far: the ledger itself, and the ~40 kbps audio figure it produced.&lt;/p&gt;

&lt;h2&gt;
  
  
  The measurement plan (in order)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Byte ledger:&lt;/strong&gt; built. Already produced the ~40 kbps audio figure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full state vs delta:&lt;/strong&gt; at what point re-sending everything beats chained deltas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Binary vs JSON:&lt;/strong&gt; real sizes and parse times on actual payloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batching:&lt;/strong&gt; grouping changes into bursts for the degraded channel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reconnect:&lt;/strong&gt; resume without duplicated work or data floods.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal store-and-forward:&lt;/strong&gt; a local queue that holds and delivers when the channel returns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audio:&lt;/strong&gt; minimum tolerable quality tier. (Opus range above is the starting point.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avatar vs low-res video:&lt;/strong&gt; the semantic-vs-pixel trade-off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prioritization:&lt;/strong&gt; given a byte budget, what goes first.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Demo (draw in a room, stroke arrives): &lt;a href="https://whiteboard-five-gamma.vercel.app" rel="noopener noreferrer"&gt;https://whiteboard-five-gamma.vercel.app&lt;/a&gt;. References: &lt;a href="https://en.wikipedia.org/wiki/Run-length_encoding" rel="noopener noreferrer"&gt;RLE&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer" rel="noopener noreferrer"&gt;MDN ArrayBuffer&lt;/a&gt;, &lt;a href="https://opus-codec.org/" rel="noopener noreferrer"&gt;Opus&lt;/a&gt; (&lt;a href="https://www.rfc-editor.org/rfc/rfc6716" rel="noopener noreferrer"&gt;RFC 6716&lt;/a&gt;), &lt;a href="https://www.rfc-editor.org/rfc/rfc6143" rel="noopener noreferrer"&gt;VNC (RFC 6143)&lt;/a&gt;, &lt;a href="https://www.rfc-editor.org/rfc/rfc4838" rel="noopener noreferrer"&gt;DTN (RFC 4838)&lt;/a&gt; (&lt;a href="https://www.rfc-editor.org/rfc/rfc9171" rel="noopener noreferrer"&gt;RFC 9171&lt;/a&gt;), ASCII experiments (&lt;a href="https://www.youtube.com/@ThePrimeagen" rel="noopener noreferrer"&gt;ThePrimeagen&lt;/a&gt;). The lab is LOW-NET: an experiment in communicating meaning instead of unnecessarily communicating state.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm asking
&lt;/h2&gt;

&lt;p&gt;If you've measured compression thresholds on tiny payloads, built avatar-based presence, or run anything over genuinely broken networks, I want to compare notes. What surprised you? Where did your model break?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>testing</category>
    </item>
    <item>
      <title>Let the Link Carry the Tracking: UTM, One Table, and Knowing When You Actually Need Signatures</title>
      <dc:creator>Armando</dc:creator>
      <pubDate>Sat, 12 Sep 2026 01:13:31 +0000</pubDate>
      <link>https://dev.to/armando284/let-the-link-carry-the-tracking-utm-one-table-and-knowing-when-you-actually-need-signatures-16p9</link>
      <guid>https://dev.to/armando284/let-the-link-carry-the-tracking-utm-one-table-and-knowing-when-you-actually-need-signatures-16p9</guid>
      <description>&lt;p&gt;The same offer goes into a Facebook group, a WhatsApp status, a Telegram channel and a classifieds site. The platform won't tell you which one works — but the link you publish can.&lt;/p&gt;

&lt;p&gt;Before the code, four things you should never confuse:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;which &lt;strong&gt;tagged link&lt;/strong&gt; generated the visit;&lt;/li&gt;
&lt;li&gt;which &lt;strong&gt;platform&lt;/strong&gt; the visitor actually came from;&lt;/li&gt;
&lt;li&gt;which visit turned into an &lt;strong&gt;inquiry&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;which inquiry turned into a &lt;strong&gt;sale&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;UTM answers #1. It never answers #2. Conversations are #3, sales are #4 — see "clicks are not sales" below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 0 — a table that measures something
&lt;/h2&gt;

&lt;p&gt;No UTM is useful without a place to land. A minimal events table is enough; no data warehouse, no customer SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;marketing_events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;medium&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;campaign&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;content&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;source/medium/campaign/content&lt;/code&gt; start empty and get filled once you tag links. If storage is your constraint, aggregate by day instead of storing every event.&lt;/p&gt;

&lt;p&gt;Storage depends on deployment, not on fashion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQLite&lt;/strong&gt; — right on a small persistent server (one local file, no external service).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL/Supabase&lt;/strong&gt; — right when that's already your stack (add a table, not a service).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serverless&lt;/strong&gt; (Vercel, Lambda) — the local filesystem is ephemeral: per-invocation, recycled, sometimes read-only. A SQLite file there is silent data loss. Put the database outside the function.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1 — tag the link, don't beg the platform
&lt;/h2&gt;

&lt;p&gt;The platform sends the full URL when someone clicks. So before publishing, embed what you need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://your-site.com/offer?utm_source=facebook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The label travels with the click. The platform is just transport.&lt;/p&gt;

&lt;p&gt;The four parameters you'll actually use:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Param&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;utm_source&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;origin you want to tell apart&lt;/td&gt;
&lt;td&gt;&lt;code&gt;facebook&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;utm_medium&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;channel type&lt;/td&gt;
&lt;td&gt;&lt;code&gt;social&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;utm_campaign&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;promotion grouping&lt;/td&gt;
&lt;td&gt;&lt;code&gt;camisas-2026-09&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;utm_content&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;specific variant of the piece&lt;/td&gt;
&lt;td&gt;&lt;code&gt;grupo-venta&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same campaign, three sources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;?utm_source=facebook&amp;amp;utm_medium=social&amp;amp;utm_campaign=camisas-2026-09&amp;amp;utm_content=grupo-venta
?utm_source=whatsapp&amp;amp;utm_medium=messaging&amp;amp;utm_campaign=camisas-2026-09&amp;amp;utm_content=estado
?utm_source=revolico&amp;amp;utm_medium=classified&amp;amp;utm_campaign=camisas-2026-09&amp;amp;utm_content=anuncio-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start with only &lt;code&gt;utm_source&lt;/code&gt; if you want; the rest is added when a question demands it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explicit assumption:&lt;/strong&gt; you control the destination page (to read the query string) and you generate the links before posting. If a shortener or a third party generates them, you don't own the data anymore.&lt;/p&gt;

&lt;h2&gt;
  
  
  What UTM does and doesn't prove
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Does:&lt;/strong&gt; tells you which labeled link was used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Doesn't:&lt;/strong&gt; tell you the physical platform. Links get copied and forwarded; a "facebook" link clicked from WhatsApp still reads &lt;code&gt;utm_source=facebook&lt;/code&gt;. That's inherent to the method, not a bug of your code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;Referer&lt;/code&gt; header is a &lt;em&gt;secondary&lt;/em&gt; signal only: browsers default to &lt;code&gt;strict-origin-when-cross-origin&lt;/code&gt; (cross-origin sends just the origin), some policies strip it, apps often send none. Don't build primary attribution on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — one axis per column, then GROUP BY
&lt;/h2&gt;

&lt;p&gt;Giant compound labels are an antipattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;utm_source=facebook-group-1-shirts-september-blue-post
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep dimensions separate so each question has its own axis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&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="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;marketing_events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'camisas-2026-09'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;source&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;DESC&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;source&lt;/code&gt; → which platform works. &lt;code&gt;campaign&lt;/code&gt; → whether the whole campaign worked. Same campaign, many sources, comparable without losing identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — single source of truth + one adapter per channel
&lt;/h2&gt;

&lt;p&gt;The offer exists once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oferta-2026-09-camisas&lt;/span&gt;
&lt;span class="na"&gt;precio_cup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2300&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each channel carries its own tracking + copy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;canales&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grupo-venta&lt;/span&gt;
    &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;facebook&lt;/span&gt;
    &lt;span class="na"&gt;medium&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;social&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grupo-venta&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;estado-wa&lt;/span&gt;
    &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;whatsapp&lt;/span&gt;
    &lt;span class="na"&gt;medium&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;messaging&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;estado&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;revolico&lt;/span&gt;
    &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;revolico&lt;/span&gt;
    &lt;span class="na"&gt;medium&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;classified&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;anuncio-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;URLs are generated from that config, so a price change is one edit and tagging can't be forgotten. Channels differ in text, image, tone and CTA, hence an adapter interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oferta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;canal&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ADAPTERS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;canal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;oferta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;oferta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tracking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;tracking_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;canal&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;What you published is a &lt;em&gt;separate&lt;/em&gt; concern from analytics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;publicaciones&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;oferta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;canal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;pieza_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;publicado_at&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;pieza_hash&lt;/code&gt; detects content changes so you don't republish the same piece twice — deduplication/idempotency, not security.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — clicks are not sales
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Channel&lt;/th&gt;
&lt;th&gt;Visits&lt;/th&gt;
&lt;th&gt;Inquiries&lt;/th&gt;
&lt;th&gt;Sales&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Facebook&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WhatsApp&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Classifieds&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Classifieds won traffic; WhatsApp won business. Publication → visit auto-measures. Visit → inquiry → sale doesn't: record it (one column, a sheet), start manual, automate only when volume justifies it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 — opaque IDs (why, when)
&lt;/h2&gt;

&lt;p&gt;When you need URLs that don't expose campaign internals (shorter, stable, non-editable):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;?c=7f3a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;7f3a&lt;/code&gt; → a row with campaign/channel/content. It doesn't make attribution truer; it changes representation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 — signatures (why almost never, and when yes)
&lt;/h2&gt;

&lt;p&gt;HMAC with a shared secret verifies one thing: a value was generated by whoever holds the secret.&lt;/p&gt;

&lt;p&gt;Use it when the parameter &lt;strong&gt;has consequences&lt;/strong&gt;: coupons, discounts, affiliate links, attribution that pays someone.&lt;/p&gt;

&lt;p&gt;Verbatim non-goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it does &lt;strong&gt;not&lt;/strong&gt; prove the platform of origin;&lt;/li&gt;
&lt;li&gt;it does &lt;strong&gt;not&lt;/strong&gt; stop someone reusing a legitimate link 300 times (replay of a valid link is still a valid link);&lt;/li&gt;
&lt;li&gt;it does &lt;strong&gt;not&lt;/strong&gt; make tracking trustworthy in general — only the parameter it signs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Decision checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Just started → events table + &lt;code&gt;utm_source&lt;/code&gt;. Stop there.&lt;/li&gt;
&lt;li&gt;Several channels per campaign → add &lt;code&gt;medium/campaign/content&lt;/code&gt;, group by axis.&lt;/li&gt;
&lt;li&gt;Growing ≥3 places → single source YAML + adapters + generate URLs.&lt;/li&gt;
&lt;li&gt;Need to know what to keep publishing → manual conversion column before any dashboard.&lt;/li&gt;
&lt;li&gt;URL looks too informative / needs to change scheme without breaking → opaque ID.&lt;/li&gt;
&lt;li&gt;Parameter has money attached to it → HMAC. Otherwise don't.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The channel ranking is a &lt;code&gt;GROUP BY&lt;/code&gt;. The data is yours. You don't need an enterprise tag manager to answer "which publication should I keep posting?"&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Adapted from my original post on &lt;a href="https://tallerweb-cu.vercel.app" rel="noopener noreferrer"&gt;TallerWeb&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>analytics</category>
      <category>socialmedia</category>
      <category>programming</category>
    </item>
    <item>
      <title>Authorized Web Audit: When the App Has No Backend, You Audit Its Assumptions</title>
      <dc:creator>Armando</dc:creator>
      <pubDate>Sat, 12 Sep 2026 01:06:36 +0000</pubDate>
      <link>https://dev.to/armando284/authorized-web-audit-when-the-app-has-no-backend-you-audit-its-assumptions-102c</link>
      <guid>https://dev.to/armando284/authorized-web-audit-when-the-app-has-no-backend-you-audit-its-assumptions-102c</guid>
      <description>&lt;p&gt;Field notes from an authorized audit of a small web store. The headline: there was no backend to attack, so the real findings were &lt;strong&gt;identifier manipulation (business logic)&lt;/strong&gt; and &lt;strong&gt;clickjacking&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup / hypothesis&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Small store on Vercel: catalog, product pages, cart, share-cart URLs, contact buttons.&lt;/li&gt;
&lt;li&gt;Looked like Next.js. Turned out to be a &lt;strong&gt;React + Vite SPA&lt;/strong&gt; — the initial misread that set the approach.&lt;/li&gt;
&lt;li&gt;Mindset: from "how do I protect this?" to &lt;strong&gt;"how can I break it?"&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1 — Recon (domain only)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;robots.txt&lt;/code&gt;, &lt;code&gt;sitemap.xml&lt;/code&gt;, routes and params, product/cart flows, HTTP headers.&lt;/li&gt;
&lt;li&gt;Result: &lt;strong&gt;0 findings&lt;/strong&gt;. No magic &lt;code&gt;/admin&lt;/code&gt;, nothing in the sitemap.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 2 — "Where's the server?"
&lt;/h2&gt;

&lt;p&gt;Expected &lt;code&gt;/api/products&lt;/code&gt;, &lt;code&gt;/api/cart&lt;/code&gt;. They did not exist.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -s https://TARGET/api/products
→ 404
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app was a SPA with no server and no database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implication for the scope:&lt;/strong&gt; no server-side attack surface → no SQLi, no auth-bypass/authorization against an API, no server-side HTML injection. When you can't attack an API, you attack the &lt;em&gt;assumptions&lt;/em&gt; of the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — ID manipulation
&lt;/h2&gt;

&lt;p&gt;Walk identifiers through real flows: &lt;code&gt;/producto/1 → 2 → 3 …&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finding 1:&lt;/strong&gt; products shown as &lt;strong&gt;sold out&lt;/strong&gt; became reachable by changing the ID within certain flows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Classification note (do this in your own reports):&lt;/strong&gt; this is NOT classic IDOR. IDOR means a server protects a resource; there is no server here. The precise label is &lt;strong&gt;identifier manipulation + insufficient business-logic validation&lt;/strong&gt;. Classifying correctly is security work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — Share-cart tampering
&lt;/h2&gt;

&lt;p&gt;Share-cart URLs could be modified to introduce sold-out products. The logic accepted states the owner never intended (a stock state the business had ruled out).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 — Headers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -I https://TARGET
X-Frame-Options: (absent)
Content-Security-Policy: frame-ancestors (absent)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app could be framed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 — Clickjacking + combined PoC
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://TARGET"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It loaded. The store was embeddable.&lt;/p&gt;

&lt;p&gt;Combined PoC: external page loading a &lt;em&gt;tampered cart URL&lt;/em&gt; in an iframe + own visual elements on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scoped results
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Not obtained:&lt;/strong&gt; passwords, RCE, server access, DB, control of the app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demonstrated:&lt;/strong&gt; state manipulation, manipulable cart, embeddable app, and the combination. Business angle: a third-party page rendering the legit store inside it → price/availability confusion, loss of trust, visual impersonation, complaints hitting an innocent owner.&lt;/p&gt;

&lt;h2&gt;
  
  
  The patch
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X-Frame-Options: DENY        # or CSP frame-ancestors 'none'
# business logic: stop trusting client data; validate availability on every flow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Retest
&lt;/h2&gt;

&lt;p&gt;Retried the attack after the fix → &lt;strong&gt;iframe no longer loads&lt;/strong&gt;. Prefer "I tried again and it no longer works" over "I think it's fixed."&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Architecture = attack surface.&lt;/strong&gt; React + Vite ≠ frontend + API + backend + DB, even if the customer sees the same shop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scanners miss business logic.&lt;/strong&gt; No tool asks "should I be able to add a sold-out product to the cart?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attacks start with small questions.&lt;/strong&gt; Change the ID. Change the URL. Frame the page. Then combine.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AI was used as a second opinion (stack identification). The decisive question stayed human: &lt;em&gt;"What happens if I use this functionality in a way the developer didn't expect?"&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Adapted from my original post on &lt;a href="https://tallerweb-cu.vercel.app" rel="noopener noreferrer"&gt;TallerWeb&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Optimizing for a 120 kbps Network That Drops: Do Less, Cache with Boundaries, Survive Offline</title>
      <dc:creator>Armando</dc:creator>
      <pubDate>Sat, 12 Sep 2026 00:55:35 +0000</pubDate>
      <link>https://dev.to/armando284/optimizing-for-a-120-kbps-network-that-drops-do-less-cache-with-boundaries-survive-offline-2fg9</link>
      <guid>https://dev.to/armando284/optimizing-for-a-120-kbps-network-that-drops-do-less-cache-with-boundaries-survive-offline-2fg9</guid>
      <description>&lt;p&gt;Reality check first: median fixed-connection download in Cuba is &lt;strong&gt;3.48 Mbps&lt;/strong&gt; vs &lt;strong&gt;104.43 Mbps&lt;/strong&gt; worldwide (Ookla via DataReportal), and the working condition is closer to &lt;strong&gt;120 kbps&lt;/strong&gt;, dropping at any moment.&lt;/p&gt;

&lt;p&gt;The wrong question is "how do I make my app faster?". The right one:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How much work does my app actually need?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Optimization starts by &lt;em&gt;not doing&lt;/em&gt;: not downloading, not computing, not requesting, not rendering, not sending what isn't necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 1 — eliminate before compressing
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Data that is never sent beats data that is compressed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Rule 2 — an API is a resource; return only what's used
&lt;/h2&gt;

&lt;p&gt;Every request costs: DNS, connection, encryption, latency, server work, serialization, parsing, battery, user time. Don't return fields the component doesn't read.&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;BAD:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;unnecessary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;fields&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(images,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;supplier,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;reviews,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;metadata)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;GOOD:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;only&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;what&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;component&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;uses&lt;/span&gt;&lt;span class="w"&gt;
&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"linen shirt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2300&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;h2&gt;
  
  
  Rule 3 — images: real resized sets, not one giant file
&lt;/h2&gt;

&lt;p&gt;Shipping a 2400×1800 photo for a 300×225 thumbnail transports data you never need. Generate a set — 160/400/800/1600 px — and let the browser pick:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
  &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"img-160.webp 160w, img-400.webp 400w, img-800.webp 800w"&lt;/span&gt;
  &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"(max-width: 600px) 100vw, 400px"&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"img-400.webp"&lt;/span&gt;
  &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"product"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: &lt;strong&gt;lazy loading ≠ size optimization.&lt;/strong&gt; &lt;code&gt;loading="lazy"&lt;/code&gt; delays when a file downloads; it doesn't make it smaller.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 4 — run less code
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Debounce&lt;/strong&gt; a search box so &lt;code&gt;c&lt;/code&gt;, &lt;code&gt;ca&lt;/code&gt;, &lt;code&gt;cam&lt;/code&gt;, &lt;code&gt;cama&lt;/code&gt; fire one request, not four.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memoize&lt;/strong&gt; &lt;code&gt;f(x)&lt;/code&gt; when recomputation is cheap compared to the network.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Virtualize&lt;/strong&gt; long lists: 10,000 rows → render the ~20 visible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rule 5 — cache explicitly, with boundaries
&lt;/h2&gt;

&lt;p&gt;Pattern: cache hit → return; miss → API → store → return. Benefits: latency, requests, server load, and — decisively — less &lt;em&gt;dependency&lt;/em&gt; on the network.&lt;/p&gt;

&lt;p&gt;Boundaries: memory is finite, so pick an eviction policy. LRU (Least Recently Used) evicts the least-recently-touched entry. I wrote &lt;a href="https://github.com/Armando284/adev-lru" rel="noopener noreferrer"&gt;&lt;code&gt;adev-lru&lt;/code&gt;&lt;/a&gt; for exactly this. The real decision isn't the library — it's &lt;em&gt;which information deserves to stay available locally&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 6 — a cache can lie; bound the truth
&lt;/h2&gt;

&lt;p&gt;DB says $550, cache says $500 → two versions of reality. Controls: &lt;strong&gt;TTL&lt;/strong&gt;, invalidation, &lt;strong&gt;stale-while-revalidate&lt;/strong&gt;, &lt;strong&gt;ETag&lt;/strong&gt;, versioning, cache-aside.&lt;/p&gt;

&lt;p&gt;Offline-first ≠ offline forever. Show cache → consult server → update.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 7 — writing with no network: a local queue
&lt;/h2&gt;

&lt;p&gt;Order created, address edited, product removed, all offline → hold operations in a &lt;strong&gt;local queue&lt;/strong&gt;, sync when the network returns.&lt;/p&gt;

&lt;p&gt;The question flips from &lt;em&gt;"how do I make the internet never fail?"&lt;/em&gt; to &lt;em&gt;"how does my app survive when it fails?"&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 8 — idempotency (the duplicate order)
&lt;/h2&gt;

&lt;p&gt;Server got the POST; the &lt;code&gt;200 OK&lt;/code&gt; was lost; client retries → &lt;strong&gt;orders #123 and #124&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /orders
Idempotency-Key: 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Retry with the same key → same order, no duplicate. A network failure is not an operation failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 9 — conflicts between devices (a lesson in smaller than you think)
&lt;/h2&gt;

&lt;p&gt;Two offline devices edit the same record: price 500 vs 550. You now own a distributed-system problem. Options, with trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;optimistic concurrency / versioning — reject on write conflict, require the user to reload;&lt;/li&gt;
&lt;li&gt;timestamps + last-write-wins — simple, silently loses the older edit;&lt;/li&gt;
&lt;li&gt;eventual consistency — eventually everybody agrees; decide if the data tolerates it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pick explicitly; a cache is a place where "the data tolerates it" should be questioned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this belongs
&lt;/h2&gt;

&lt;p&gt;Performance as a &lt;em&gt;later stage&lt;/em&gt; (build → publish → measure → optimize) gets you a fast app on a good network and nothing on a bad one. Under hard constraints the decisions live in the &lt;strong&gt;architecture&lt;/strong&gt;: how much is downloaded and stored, and what happens when the connection fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Never send what isn't needed.&lt;/li&gt;
&lt;li&gt;An API response is a payload, not a promise — return used fields only.&lt;/li&gt;
&lt;li&gt;Images: resized sets + &lt;code&gt;srcset&lt;/code&gt;/&lt;code&gt;sizes&lt;/code&gt; + modern formats; lazy loading doesn't shrink files.&lt;/li&gt;
&lt;li&gt;Debounce, memoize, virtualize — run less code.&lt;/li&gt;
&lt;li&gt;Cache with a boundary (LRU) and correct-yourself mechanisms (TTL/SWR/ETag).&lt;/li&gt;
&lt;li&gt;Offline writes belong in a queue; retries need an &lt;code&gt;Idempotency-Key&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Conflicts are distributed-system problems, even on one laptop.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Internet is a dependency. Architecture decides how much of it you need.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Adapted from my original post on &lt;a href="https://tallerweb-cu.vercel.app" rel="noopener noreferrer"&gt;TallerWeb&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>performance</category>
      <category>webdev</category>
      <category>webperf</category>
    </item>
    <item>
      <title>What building a life simulation taught me about probability and my father’s faith.</title>
      <dc:creator>Armando</dc:creator>
      <pubDate>Wed, 06 May 2026 02:43:54 +0000</pubDate>
      <link>https://dev.to/armando284/what-building-a-life-simulation-taught-me-about-probability-and-my-fathers-faith-4no2</link>
      <guid>https://dev.to/armando284/what-building-a-life-simulation-taught-me-about-probability-and-my-fathers-faith-4no2</guid>
      <description>&lt;p&gt;Some conversations stay with you longer than most books. One of the most memorable I've had was with my father. He argued that life must be the direct work of God because it was simply too improbable to arise on its own.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Look at everything that had to happen for us to be here," he said. "This can't be a coincidence."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I understood his intuition perfectly. Life does feel like a miracle. But something in his reasoning felt off, not because I don't believe in God, but because I felt we were reading the universe backwards.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Mistake of Looking at the Tree from the Last&amp;nbsp;Leaf
&lt;/h3&gt;

&lt;p&gt;I told my father that his argument was like examining a &lt;a href="https://en.wikipedia.org/wiki/Decision_tree" rel="noopener noreferrer"&gt;decision tree&lt;/a&gt; starting from the final leaf. When you begin at the end, your specific existence, your exact life, of course, everything looks impossibly lucky. Every unlikely turn, every fortunate event, every precise alignment seems miraculous.&lt;br&gt;
But that feeling is an illusion. It's not that this path was special. It's that any path would have looked special once you reached the end of it.&lt;br&gt;
A simple example: Flip a coin 1,000 times. Whatever sequence you get, heads, tails, tails, heads…, will be astronomically improbable. Yet every possible sequence is equally unlikely. One of them had to occur.&lt;br&gt;
Life is that sequence: improbable, yes, but not more so than any other outcome.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Complexity Doesn't Need Magic, Just Simple&amp;nbsp;Rules
&lt;/h3&gt;

&lt;p&gt;To make my point clearer, I turned to something that has fascinated me for years as a software engineer: &lt;a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life?utm_source=copilot.com" rel="noopener noreferrer"&gt;Conway's Game of Life&lt;/a&gt;.&lt;br&gt;
It's a minimalist "universe" governed by ridiculously simple rules:&lt;br&gt;
A cell lives or dies based only on how many neighbors it has.&lt;br&gt;
No purpose. No design. No intelligence behind it.&lt;/p&gt;

&lt;p&gt;And yet, from these basic rules emerge:&lt;br&gt;
Gliders that move across the grid&lt;br&gt;
Oscillators and spaceships&lt;br&gt;
Self-replicating patterns&lt;br&gt;
Structures that look almost engineered&lt;/p&gt;

&lt;p&gt;The first time I saw complex behavior emerge from nothing, my mind was blown. So, like any curious programmer, I built my own version.&lt;br&gt;
You can try it here: &lt;a href="https://conway-s-life-game.vercel.app/" rel="noopener noreferrer"&gt;Conway's Game of Life&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repository if you want to check the code: &lt;a href="https://github.com/Armando284/Conway-s-Life-Game" rel="noopener noreferrer"&gt;github.com/Armando284/Conway-s-Life-Game&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I implemented it in vanilla JavaScript with Canvas, optimizing the neighbor-counting logic and only adding controls to pause, adjust speed, and draw custom patterns. Watching random noise slowly organize into moving, breathing structures felt like witnessing a tiny universe learning to live.&lt;br&gt;
I showed this to my father: "Look, no miracles, no designer inside the simulation. Just rules. And complexity appears anyway."&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Artificial Evolution: When Life Emerges Without Being&amp;nbsp;Pushed
&lt;/h3&gt;

&lt;p&gt;I also shared another project I built: a simulation where simple creatures with neural networks evolve to survive in a 2D environment.&lt;br&gt;
Repository: &lt;a href="https://github.com/Armando284/life-simulation" rel="noopener noreferrer"&gt;github.com/Armando284/life-simulation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this simulation:&lt;br&gt;
Each creature has a small neural network (inputs: sensors for food, obstacles, and energy; outputs: movement decisions).&lt;br&gt;
They must navigate toward food while managing limited energy.&lt;br&gt;
Only the ones that reach the goal reproduce, passing on slightly mutated "brains."&lt;br&gt;
Over generations, surprisingly smart behaviors emerge, creatures learn to dodge obstacles, cluster around food, and move efficiently.&lt;/p&gt;

&lt;p&gt;No one programmed the final strategies. The intelligence wasn't designed; it evolved through mutation, selection, and millions of small steps.&lt;br&gt;
This is neuroevolution in action: combining neural networks with genetic algorithms. What started as random wiggling slowly turned into purposeful movement. It was mesmerizing to watch.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Why Our Intuition Fails with Probability
&lt;/h3&gt;

&lt;p&gt;My father kept insisting life felt too special. He was right, but not in the way he thought.&lt;br&gt;
Life is special to us. But the universe doesn't share our perspective. Our brains evolved for survival, not for understanding deep time or vast probabilities. We overestimate how "unlikely" a specific outcome is once it has already happened.&lt;br&gt;
The truly surprising thing isn't that life emerged. It's that a universe with the right physical constants and rules wouldn't eventually produce it.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. God Doesn't Need to Break His Own&amp;nbsp;Rules
&lt;/h3&gt;

&lt;p&gt;I closed our conversation with something said with love and respect:&lt;br&gt;
"I do believe in God. But I don't think He has to break the rules of the universe to create what exists. If God created the rules, then life is a natural and beautiful consequence of them."&lt;br&gt;
To me, this makes the universe even more elegant. A system where complexity and life can emerge on their own is more admirable than one that requires constant intervention.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. What This Conversation Left Me&amp;nbsp;With
&lt;/h3&gt;

&lt;p&gt;I didn't convince my father, and he didn't convince me. That's okay. What mattered was the exchange, thoughtful, respectful, and full of wonder.&lt;br&gt;
Life can be both improbable and inevitable. It can be deeply special without being magical. It can be extraordinarily complex without needing a designer at every step.&lt;br&gt;
And above all, searching for explanations doesn't diminish the beauty of the world. It multiplies it.&lt;/p&gt;

</description>
      <category>simulation</category>
      <category>javascript</category>
      <category>machinelearning</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Enhancing Cache with Configurable Data Persistence</title>
      <dc:creator>Armando</dc:creator>
      <pubDate>Sun, 22 Dec 2024 07:20:12 +0000</pubDate>
      <link>https://dev.to/armando284/enhancing-lru-cache-with-configurable-data-persistence-37am</link>
      <guid>https://dev.to/armando284/enhancing-lru-cache-with-configurable-data-persistence-37am</guid>
      <description>&lt;p&gt;Building on the foundations of &lt;a href="https://dev.to/armando284/how-to-create-an-in-memory-cache-3ihe"&gt;this guide&lt;/a&gt; on creating an in-memory cache, we’ll take it further by introducing &lt;strong&gt;configurable data persistence&lt;/strong&gt;. By leveraging the Adapter and Strategy patterns, we’ll design an extensible system that decouples storage mechanisms from the caching logic, allowing seamless integration of databases or services as needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Vision: Decoupling Like an ORM
&lt;/h2&gt;

&lt;p&gt;The goal is to make the cache extensible without altering its core logic. Inspired by ORM systems, our approach involves a &lt;strong&gt;shared API abstraction&lt;/strong&gt;. This allows storage — such as &lt;code&gt;localStorage&lt;/code&gt;, &lt;code&gt;IndexedDB&lt;/code&gt;, or even a remote database—to work interchangeably with minimal code changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Storage Adapter Base Class
&lt;/h3&gt;

&lt;p&gt;Here’s the abstract class defining the API for any persistence system:&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;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StorageAdapter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&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;value&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&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="nb"&gt;Promise&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;|&lt;/span&gt; &lt;span class="kc"&gt;null&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;abstract&lt;/span&gt; &lt;span class="nf"&gt;getAll&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;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;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any storage solution must extend this base class, ensuring consistency in interaction. For instance, here’s the implementation for &lt;code&gt;IndexedDB&lt;/code&gt;:&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: IndexedDB Adapter
&lt;/h2&gt;

&lt;p&gt;This adapter implements the &lt;code&gt;StorageAdapter&lt;/code&gt; interface to persist cache data in an IndexedDB 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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StorageAdapter&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;./storage_adapter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * IndexedDBAdapter is an implementation of the StorageAdapter 
 * interface designed to provide a persistent storage mechanism 
 * using IndexedDB. This adapter can be reused for other cache 
 * implementations or extended for similar use cases, ensuring 
 * flexibility and scalability.
 */&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;IndexedDBAdapter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;StorageAdapter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;dbName&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="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;storeName&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="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IDBDatabase&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Initializes the adapter with the specified database and store 
   * names. Defaults are provided to make it easy to set up without 
   * additional configuration.
   */&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;dbName&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;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cacheDB&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;storeName&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;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cacheStore&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;super&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dbName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dbName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storeName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;storeName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Connects to the IndexedDB database and initializes it if 
   * necessary. This asynchronous method ensures that the database 
   * and object store are available before any other operations. 
   * It uses the `onupgradeneeded` event to handle schema creation 
   * or updates, making it a robust solution for versioning.
   */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&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;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;indexedDB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dbName&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="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onupgradeneeded&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="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;db&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;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;IDBOpenDBRequest&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objectStoreNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storeName&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&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;createObjectStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storeName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;keyPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;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="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onsuccess&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&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;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;IDBOpenDBRequest&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;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

      &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&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;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&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;/**
   * Adds or updates a key-value pair in the store. This method is 
   * asynchronous to ensure compatibility with the non-blocking 
   * nature of IndexedDB and to prevent UI thread blocking. Using 
   * the `put` method ensures idempotency: the operation will 
   * insert or replace the entry.
   */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&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;value&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_withTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;readwrite&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;store&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;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Retrieves the value associated with a key. If the key does not 
   * exist, null is returned. This method is designed to integrate 
   * seamlessly with caching mechanisms, enabling fast lookups.
   */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&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="nb"&gt;Promise&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;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_withTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;readonly&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;store&lt;/span&gt;&lt;span class="p"&gt;)&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="nf"&gt;_promisifyRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;then&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="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&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;value&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="nx"&gt;value&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="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Fetches all key-value pairs from the store. Returns an object 
   * mapping keys to their values, making it suitable for bulk 
   * operations or syncing with in-memory caches.
   */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getAll&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;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;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_withTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;readonly&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;store&lt;/span&gt;&lt;span class="p"&gt;)&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="nf"&gt;_promisifyRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;results&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;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="na"&gt;acc&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="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;item&lt;/span&gt;&lt;span class="p"&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;value&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;acc&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="cm"&gt;/**
   * Deletes a key-value pair by its key. This method is crucial 
   * for managing cache size and removing expired entries. The 
   * `readwrite` mode is used to ensure proper deletion.
   */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_withTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;readwrite&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;store&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;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Clears all entries from the store. This method is ideal for 
   * scenarios where the entire cache needs to be invalidated, such 
   * as during application updates or environment resets.
   */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_withTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;readwrite&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;store&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;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Handles transactions in a reusable way. Ensures the database 
   * is connected and abstracts the transaction logic. By 
   * centralizing transaction handling, this method reduces 
   * boilerplate code and ensures consistency across all operations.
   */&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;_withTransaction&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IDBTransactionMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IDBObjectStore&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;IDBRequest&lt;/span&gt; &lt;span class="o"&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;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&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;IndexedDB is not connected&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;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storeName&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;objectStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storeName&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;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;IDBRequest&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;await&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;_promisifyRequest&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="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Converts IndexedDB request events into Promises, allowing for 
   * cleaner and more modern asynchronous handling. This is 
   * essential for making IndexedDB operations fit seamlessly into 
   * the Promise-based architecture of JavaScript applications.
   */&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;_promisifyRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IDBRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&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;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onsuccess&lt;/span&gt; &lt;span class="o"&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;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&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;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&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;
  
  
  Integrating the Adapter into the Cache
&lt;/h2&gt;

&lt;p&gt;The cache accepts an optional &lt;code&gt;StorageAdapter&lt;/code&gt;. If provided, it initializes the database connection, loads data into memory, and keeps the cache and storage in sync.&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;private&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;capacity&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;storageAdapter&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;StorageAdapter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storageAdapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;storageAdapter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storageAdapter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storageAdapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&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;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storageAdapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="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="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&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;T&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;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&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;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&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;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tail&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hitCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;missCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;evictionCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Adapter and Strategy Patterns?
&lt;/h2&gt;

&lt;p&gt;Using the Adapter pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decouples&lt;/strong&gt; the cache from specific storage mechanisms.&lt;/li&gt;
&lt;li&gt;Ensures &lt;strong&gt;extensibility&lt;/strong&gt; for new storage backends.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combining with the Strategy pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enables runtime selection of the persistence layer.&lt;/li&gt;
&lt;li&gt;Simplifies testing by mocking different adapters.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Design Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Abstract API:&lt;/strong&gt; Keeps the cache logic agnostic of storage details.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Singleton Cache:&lt;/strong&gt; Ensures shared state consistency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Async Initialization:&lt;/strong&gt; Avoids blocking operations during setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lazy Loading:&lt;/strong&gt; Only loads persisted data when a storage adapter is provided.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;This design is robust but leaves room for enhancements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Optimize sync logic for better performance.&lt;/li&gt;
&lt;li&gt;Experiment with additional adapters like Redis or SQLite.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It Out! 🚀
&lt;/h2&gt;

&lt;p&gt;If you’d like to test the cache in action, it’s available as an npm package: &lt;a href="https://www.npmjs.com/package/adev-lru" rel="noopener noreferrer"&gt;adev-lru&lt;/a&gt;. You can also explore the full source code on GitHub: &lt;a href="https://github.com/Armando284/adev-lru" rel="noopener noreferrer"&gt;adev-lru repository&lt;/a&gt;. I welcome any recommendations, constructive feedback, or contributions to make it even better! 💡&lt;/p&gt;

&lt;p&gt;Happy coding! 🚀&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>How to Create an In-Memory Cache</title>
      <dc:creator>Armando</dc:creator>
      <pubDate>Wed, 18 Dec 2024 14:14:04 +0000</pubDate>
      <link>https://dev.to/armando284/how-to-create-an-in-memory-cache-3ihe</link>
      <guid>https://dev.to/armando284/how-to-create-an-in-memory-cache-3ihe</guid>
      <description>&lt;p&gt;In many projects, I’ve noticed that while a cache could be handy — especially on the client side— it’s often overlooked. Client-side caching is critical in enhancing user experience by reducing latency and offloading repeated server requests. For example, in applications with infinite scrolling or frequently updated dashboards, caching previously fetched data prevents unnecessary API calls, ensuring smoother interactions and faster rendering times. &lt;/p&gt;

&lt;p&gt;In one of my recent projects, implementing a cache reduced API call volume by over 40%, leading to noticeable performance improvements and cost savings. This underlines why client-side caching should be considered a foundational optimization strategy. A cache tends to be one of the last features considered, despite its significant impact on performance with relatively simple implementation, whether due to development time constraints or other priorities.&lt;/p&gt;

&lt;p&gt;A cache can be implemented at various levels in architecture: from backend caching using Redis, a CDN for static content, to an in-memory cache on the client or even using localStorage or IndexedDB for persistency. Ideally, these strategies should be combined to reduce the load and cost of databases and APIs, as well as the lag from client-server requests, especially for data that has already been fetched before.&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore how to design and implement an LRU (Least Recently Used) cache with TTL (Time-to-Live) support in JavaScript, creating a package similar to my adev-lru. By the end, you’ll have a working example that showcases the core principles and functionality of an effective caching solution.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is an LRU Cache?
&lt;/h2&gt;

&lt;p&gt;An LRU (Least Recently Used) cache ensures that the most recently accessed items remain in memory while evicting the least recently accessed ones when their capacity is exceeded. This strategy works by maintaining an order of usage: each accessory updates the item’s position in the cache, with the least accessed items being removed first. &lt;/p&gt;

&lt;p&gt;Compared to other caching strategies, LRU balances simplicity and efficiency, making it well-suited for scenarios where recent usage is a reliable indicator of future access. For example, applications that cache API responses, thumbnails, or frequently accessed user preferences can leverage LRU to reduce redundant fetch operations without over-complicating the eviction process. &lt;/p&gt;

&lt;p&gt;Unlike LFU (Least Frequently Used), which tracks access frequency and requires additional bookkeeping, LRU avoids this complexity while still achieving excellent performance in many real-world use cases. Similarly, FIFO (First In, First Out) and MRU (Most Recently Used) offer alternative eviction policies but may not align as well with usage patterns where recent activity is critical. By combining LRU with TTL (Time-to-Live) support in my implementation, it also handles scenarios where data needs automatic expiration, further enhancing its applicability in dynamic environments like live dashboards or streaming services. It’s especially useful in applications where access to the most recent data is critical.&lt;/p&gt;

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

&lt;p&gt;The &lt;code&gt;LRUCache&lt;/code&gt; class is built to be efficient, support flexible configurations, and handle automatic evictions. Below are some key methods:&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the Cache
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;LRUCache&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LRUCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;LRUCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&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;LRUCache&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;capacity&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;LRUCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method ensures there is only one instance of the cache in the application, a design choice that simplifies resource management. By implementing the cache as a singleton, we avoid redundant memory usage and ensure consistent data across the application. This is particularly valuable in scenarios where multiple components or modules need access to the same cached data, as it prevents conflicts and ensures synchronization without requiring additional coordination logic. If no capacity is specified, it defaults to 10.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding an Item to the Cache
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&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;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;LRUCache&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;node&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="nf"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&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;capacity&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;tailNode&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="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tailNode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tailNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method adds or updates an item in the cache. When a key already exists, its corresponding item is evicted and re-added at the front of the cache. To do this the cache uses a Doubly Linked List to save the data as nodes and maintain the ability to delete data from the end of the list — Tail— and move it to the beginning of the list — Head —, to guarantee a constant O(1) read of every node’s data a Hash Table is used to save a pointer to each node of the list. This process aligns with the LRU principle by ensuring that recently accessed items are always prioritized, effectively marking them as “most recently used.” By doing so, the cache maintains an accurate order of usage, which is critical for making eviction decisions when the capacity is exceeded. This behavior ensures that resources are optimally managed, minimizing the retrieval time for frequently accessed data. If the key already exists, the item is moved to the front to mark it as recently used.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retrieving an Item from the Cache
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&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;T&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&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;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ttl&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;now&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="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&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;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ttl&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;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method retrieves stored items. If the item has expired, it is removed from the cache.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance Metrics
&lt;/h3&gt;

&lt;p&gt;To evaluate the cache’s efficiency, I implemented performance metrics like hit rate, misses, and evictions:&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;private&lt;/span&gt; &lt;span class="nf"&gt;getHitRate&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;totalRequests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hitCount&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;missCount&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;totalRequests&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hitCount&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;totalRequests&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;
  
  
  Clearing the Cache
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;clear&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tail&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method clears all items and resets the cache state.&lt;/p&gt;

&lt;p&gt;In my implementation, I have also added other methods like &lt;code&gt;getOption&lt;/code&gt; which instead of returning &lt;code&gt;T | undefined&lt;/code&gt; it return an instance of the monad &lt;code&gt;Option&lt;/code&gt; for those who prefer a more functional approach. I also added a &lt;code&gt;Writer&lt;/code&gt; monad to track every operation on the cache for logging purposes.&lt;/p&gt;

&lt;p&gt;You can see all the other methods involved in this algorithm, very well commented, on this repository: &lt;a href="https://github.com/Armando284/adev-lru" rel="noopener noreferrer"&gt;https://github.com/Armando284/adev-lru&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Comparing Cache Algorithms
&lt;/h2&gt;

&lt;p&gt;An LRU cache is not the only option. Choosing the right caching algorithm depends heavily on the application’s specific requirements and access patterns. Below is a comparison of LRU with other commonly used caching strategies and guidance on when to use each:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LFU (Least Frequently Used): This algorithm evicts items that are accessed the least number of times. It’s ideal for scenarios where usage frequency over time is a better predictor of future access than recency. However, it typically requires additional bookkeeping to track access counts, making it more complex and computationally expensive than LRU. Use LFU for applications like recommendation engines or machine learning pipelines where historical usage patterns are critical.&lt;/li&gt;
&lt;li&gt;FIFO (First In, First Out): This approach removes the oldest items, regardless of how often they are accessed. While it is simple to implement, FIFO might not be suitable for most use cases as it doesn’t consider usage patterns. It can work for applications with fixed, predictable workflows, such as caching static configurations or preloaded assets.&lt;/li&gt;
&lt;li&gt;MRU (Most Recently Used): MRU evicts the most recently accessed items, the opposite of LRU. This strategy is best suited for situations where older data is more likely to be reused, such as rollback systems or certain types of undo operations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to Use LRU
&lt;/h2&gt;

&lt;p&gt;LRU strikes a balance between simplicity and effectiveness, making it ideal for applications where recent activity correlates strongly with future use. For instance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web and API Caching: LRU is well-suited for reducing redundant API calls, especially in scenarios like paginated views, infinite scrolling, or frequent polling of live data.&lt;/li&gt;
&lt;li&gt;Multimedia Applications: Cache recently played or viewed items like videos or images.&lt;/li&gt;
&lt;li&gt;UI State Management: Store recently accessed component states to improve rendering performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In contrast, if access patterns show that frequency or insertion order is more relevant, algorithms like LFU or FIFO might be a better choice. Evaluating these trade-offs ensures that the caching strategy aligns with your application’s goals and resource constraints.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LFU (Least Frequently Used): Removes the least accessed items based on frequency.&lt;/li&gt;
&lt;li&gt;FIFO (First In, First Out): Evicts the oldest item, regardless of recent usage.&lt;/li&gt;
&lt;li&gt;MRU (Most Recently Used): Removes the most recently added items, contrary to LRU.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Implementing an in-memory cache can significantly enhance an application’s performance, reducing response times and improving user experience.&lt;/p&gt;

&lt;p&gt;If you want to see a full LRU Cache in action you can use my npm package &lt;a href="https://www.npmjs.com/package/adev-lru" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/adev-lru&lt;/a&gt; I would also love to get your feedback to keep improving it.&lt;/p&gt;

&lt;p&gt;Try the package and share your thoughts or contribute if you feel like helping more 😁!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>algorithms</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
