<?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: ternentdotdev</title>
    <description>The latest articles on DEV Community by ternentdotdev (@ternentdotdev).</description>
    <link>https://dev.to/ternentdotdev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F417477%2F53bbc487-a5ac-4730-b243-8331207c7453.png</url>
      <title>DEV Community: ternentdotdev</title>
      <link>https://dev.to/ternentdotdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ternentdotdev"/>
    <language>en</language>
    <item>
      <title>Your app doesn’t need a database. Here’s what it needs instead.</title>
      <dc:creator>ternentdotdev</dc:creator>
      <pubDate>Mon, 30 Mar 2026 12:47:29 +0000</pubDate>
      <link>https://dev.to/ternentdotdev/your-app-doesnt-need-a-database-heres-what-it-needs-instead-h3n</link>
      <guid>https://dev.to/ternentdotdev/your-app-doesnt-need-a-database-heres-what-it-needs-instead-h3n</guid>
      <description>&lt;p&gt;I've been building web apps for a while, and they all end up looking the same.&lt;/p&gt;

&lt;p&gt;User does something → we update a database → we serve the latest state back.&lt;/p&gt;

&lt;p&gt;It works. It scales. Everyone does it this way.&lt;/p&gt;

&lt;p&gt;But it also means the database &lt;em&gt;is&lt;/em&gt; the truth. And when the database is the truth, you have to trust whoever owns it — including yourself.&lt;/p&gt;

&lt;p&gt;I’ve had moments where that broke down. Not catastrophically — but enough to realise we had no way to prove what actually happened.&lt;/p&gt;

&lt;p&gt;State mutates. Logs drift. Backups lie.&lt;/p&gt;

&lt;p&gt;So I started exploring a different shape.&lt;/p&gt;

&lt;p&gt;The result is &lt;strong&gt;Concord&lt;/strong&gt; — an app runtime where state is never stored. It's derived from a signed, portable history.&lt;/p&gt;

&lt;p&gt;Think of it like this:&lt;/p&gt;

&lt;p&gt;Imagine a todo app where the file proves nobody has tampered with it — and you can email it to someone and they can verify it instantly.&lt;/p&gt;

&lt;p&gt;Instead of the app owning your state, you own a file that contains a signed history — and the app just projects from it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your data is just a file. You decide where it lives.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  The core idea
&lt;/h1&gt;

&lt;p&gt;Most apps store current state. Concord doesn't.&lt;/p&gt;

&lt;p&gt;Every action is appended to a history as an event. Current state is whatever you get when you replay those events from the beginning. There's nothing to mutate — just a log of what happened, and a runtime that reconstructs the present from it.&lt;/p&gt;

&lt;p&gt;This is called event sourcing.&lt;/p&gt;

&lt;p&gt;But Concord takes it further — the history isn’t hidden inside a backend. It’s portable, signed, and self-verifying.&lt;/p&gt;

&lt;p&gt;The insight is that &lt;strong&gt;history is more durable than state&lt;/strong&gt;. You can always re-derive state from history. You can't go the other way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createConcordApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;createTodoPlugin&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;todo.create-item&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Buy milk&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;todo.complete-item&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;morning tasks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;todos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getReplayState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;todo&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;Commands stage entries locally. &lt;code&gt;commit()&lt;/code&gt; seals them into history as a signed block.&lt;/p&gt;

&lt;p&gt;If you've used git, this will feel familiar.&lt;/p&gt;

&lt;p&gt;Replay is deterministic.&lt;/p&gt;




&lt;h1&gt;
  
  
  The ledger is just a file
&lt;/h1&gt;

&lt;p&gt;Not a database you connect to. A file you can open.&lt;/p&gt;

&lt;p&gt;The history — the ledger — is plain JSON.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"commits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"commitId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:key:abc123..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"parentHash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"proof"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"entries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can live anywhere. A Solid Pod. Google Drive. Dropbox. IPFS. A USB stick.&lt;/p&gt;

&lt;p&gt;You can email it. You can AirDrop it.&lt;/p&gt;

&lt;p&gt;The runtime doesn't care — storage is an adapter you plug in, not something the library owns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createConcordApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;solidPodAdapter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No storage backend? The ledger still works.&lt;/p&gt;

&lt;p&gt;Export it. Move it. Load it somewhere else.&lt;/p&gt;




&lt;h1&gt;
  
  
  Tamper-evidence is not optional
&lt;/h1&gt;

&lt;p&gt;Every entry is signed. Every commit is chained.&lt;/p&gt;

&lt;p&gt;Change any committed byte — and verification fails.&lt;/p&gt;

&lt;p&gt;The runtime refuses to derive state from corrupted history.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;verification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the ledger is invalid, the app won’t proceed.&lt;/p&gt;




&lt;h1&gt;
  
  
  Identity is a mnemonic phrase
&lt;/h1&gt;

&lt;p&gt;Your identity is derived from a mnemonic phrase.&lt;/p&gt;

&lt;p&gt;If you’ve used a hardware wallet before, it’s the same recovery model — just applied to application identity instead of money.&lt;/p&gt;

&lt;p&gt;No account. No server. No reset flow.&lt;/p&gt;

&lt;p&gt;Lose the phrase and the identity is gone. Keep it safe and you can regenerate everything.&lt;/p&gt;




&lt;h1&gt;
  
  
  Encryption is first-class
&lt;/h1&gt;

&lt;p&gt;Every entry payload has an explicit type: plain, encrypted, or decrypted.&lt;/p&gt;

&lt;p&gt;Encryption is part of the write path.&lt;/p&gt;

&lt;p&gt;Permission groups manage access. Each group has a symmetric key.&lt;/p&gt;

&lt;p&gt;Granting access is just wrapping that key for another user — no re-encryption required.&lt;/p&gt;

&lt;p&gt;The same ledger can be shared with multiple people. Each sees only what they can decrypt.&lt;/p&gt;




&lt;h1&gt;
  
  
  What this enables
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;A password vault where the data is a file you hold&lt;/li&gt;
&lt;li&gt;A task manager with a full audit history&lt;/li&gt;
&lt;li&gt;A shared document with signed, attributed changes&lt;/li&gt;
&lt;li&gt;Multi-party workflows without a central authority&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Where it's at
&lt;/h1&gt;

&lt;p&gt;Open source. Available now.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runtime: &lt;a href="https://npmjs.com/package/@ternent/concord" rel="noopener noreferrer"&gt;https://npmjs.com/package/@ternent/concord&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Ledger: &lt;a href="https://npmjs.com/package/@ternent/ledger" rel="noopener noreferrer"&gt;https://npmjs.com/package/@ternent/ledger&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Signing: &lt;a href="https://npmjs.com/package/@ternent/seal-cli" rel="noopener noreferrer"&gt;https://npmjs.com/package/@ternent/seal-cli&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Encryption: &lt;a href="https://npmjs.com/package/@ternent/armour" rel="noopener noreferrer"&gt;https://npmjs.com/package/@ternent/armour&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Most apps ask you to trust their database.&lt;/p&gt;

&lt;p&gt;Concord asks you to trust the history — and verify it yourself.&lt;/p&gt;




&lt;p&gt;Would you ever build an app like this — or is this solving a problem you don’t have?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>security</category>
    </item>
    <item>
      <title>Designing a portable proof format for signing files in JavaScript</title>
      <dc:creator>ternentdotdev</dc:creator>
      <pubDate>Tue, 17 Mar 2026 16:21:12 +0000</pubDate>
      <link>https://dev.to/ternentdotdev/designing-a-portable-proof-format-for-signing-files-in-javascript-40lf</link>
      <guid>https://dev.to/ternentdotdev/designing-a-portable-proof-format-for-signing-files-in-javascript-40lf</guid>
      <description>&lt;p&gt;I recently needed a consistent way to sign content across environments - specifically in both Node and the browser.&lt;/p&gt;

&lt;p&gt;Most existing approaches either rely on shelling out to system binaries or are tightly coupled to a particular runtime. I wanted something:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JS-native
&lt;/li&gt;
&lt;li&gt;Deterministic
&lt;/li&gt;
&lt;li&gt;Portable
&lt;/li&gt;
&lt;li&gt;With a clear, versioned proof format
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I ended up designing a small proof primitive and extracting it into its own tool.&lt;/p&gt;




&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;Given:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A file
&lt;/li&gt;
&lt;li&gt;Some text
&lt;/li&gt;
&lt;li&gt;Or a directory
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Produces a deterministic representation
&lt;/li&gt;
&lt;li&gt;Signs it locally
&lt;/li&gt;
&lt;li&gt;Emits a versioned JSON proof
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That proof can be published alongside the content and verified later. It’s self-contained and runtime-agnostic.&lt;/p&gt;

&lt;p&gt;For directories, a stable manifest is generated first, so the proof reflects the structure and contents deterministically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why JSON?
&lt;/h2&gt;

&lt;p&gt;The proof format is intentionally plain JSON.&lt;/p&gt;

&lt;p&gt;That makes it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inspectable
&lt;/li&gt;
&lt;li&gt;Easy to store
&lt;/li&gt;
&lt;li&gt;Easy to transport
&lt;/li&gt;
&lt;li&gt;Easy to verify across environments
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same proof format is used in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A CLI
&lt;/li&gt;
&lt;li&gt;A GitHub Action
&lt;/li&gt;
&lt;li&gt;A JavaScript library
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the representation doesn’t change depending on where it’s generated.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this isn’t
&lt;/h2&gt;

&lt;p&gt;It’s not trying to replace existing signing ecosystems.&lt;/p&gt;

&lt;p&gt;It’s just a minimal, consistent proof format that works natively in JavaScript across environments.&lt;/p&gt;




&lt;p&gt;If this sounds useful, it’s here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://seal.ternent.dev/" rel="noopener noreferrer"&gt;https://seal.ternent.dev/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>opensource</category>
      <category>tooling</category>
    </item>
    <item>
      <title>JSON compression in the browser, with gzip and the Compression Streams API.</title>
      <dc:creator>ternentdotdev</dc:creator>
      <pubDate>Sun, 19 Mar 2023 23:40:14 +0000</pubDate>
      <link>https://dev.to/ternentdotdev/json-compression-in-the-browser-with-gzip-and-the-compression-streams-api-4135</link>
      <guid>https://dev.to/ternentdotdev/json-compression-in-the-browser-with-gzip-and-the-compression-streams-api-4135</guid>
      <description>&lt;p&gt;&lt;strong&gt;EDIT:&lt;/strong&gt; &lt;del&gt;&lt;a href="https://caniuse.com/mdn-api_compressionstream_compressionstream_gzip" rel="noopener noreferrer"&gt;... just not yet in Firefox&lt;/a&gt;&lt;/del&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When building web apps, we sometimes find ourselves with a need to persist large JSON objects. Either directly to the browser using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API" rel="noopener noreferrer"&gt;Web Storage&lt;/a&gt;, or externally through the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/File" rel="noopener noreferrer"&gt;File&lt;/a&gt; or &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API" rel="noopener noreferrer"&gt;Fetch&lt;/a&gt; APIs.&lt;/p&gt;

&lt;p&gt;This problem lead me to ask the question: &lt;strong&gt;Is there a better solution to storing raw or encoded JSON? Can we achieve some level of compression?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As it turns out, the answer is &lt;strong&gt;Yes&lt;/strong&gt; and it's surprisingly simple with the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Compression_Streams_API" rel="noopener noreferrer"&gt;Compression Streams API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This example was created for the specific needs of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API" rel="noopener noreferrer"&gt;Web Storage&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/File" rel="noopener noreferrer"&gt;File&lt;/a&gt; and &lt;a href="https://solidproject.org/" rel="noopener noreferrer"&gt;Solid Pod&lt;/a&gt; persistence, for a scaling representation of JSON application state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;To set the scene a little, the project I am working on doesn't communicate with a server. Application data is structured transactionally in a &lt;a href="https://en.wikipedia.org/wiki/Merkle_tree" rel="noopener noreferrer"&gt;Merkle Tree&lt;/a&gt; (git, blockchain). The tree structure naturally grows with each interaction with the application and along with it, its memory and storage footprint.&lt;/p&gt;

&lt;p&gt;To keep an eye on this growth, I built a small indicator in the control bar to visualise the size. I found as the JSON object grew, the data started to reach an extent I wasn't completely comfortable with, so I decided to take a look and see if I could do better.&lt;/p&gt;

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

&lt;p&gt;This brought me to the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Compression_Streams_API" rel="noopener noreferrer"&gt;Compression Streams API&lt;/a&gt;. A browser API that enables &lt;a href="https://en.wikipedia.org/wiki/Gzip" rel="noopener noreferrer"&gt;&lt;code&gt;gzip&lt;/code&gt;&lt;/a&gt; compression on a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream" rel="noopener noreferrer"&gt;&lt;code&gt;ReadableStream&lt;/code&gt;&lt;/a&gt;, directly in the browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; After enabling &lt;code&gt;gzip&lt;/code&gt; compression on my JSON data, I saw a size reduction of 98%, from &lt;code&gt;943kb&lt;/code&gt; to &lt;code&gt;10kb&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;This wasn't a scientific test and the data didn't scale how it would organically in the app, but it serves as a fair benchmark that I will get the results I want from this API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;As the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Compression_Streams_API" rel="noopener noreferrer"&gt;Compression Streams API&lt;/a&gt; is part of the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Streams_API" rel="noopener noreferrer"&gt;Streams API&lt;/a&gt;, we first need to transform our JSON data object to a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream" rel="noopener noreferrer"&gt;ReadableStream&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob" rel="noopener noreferrer"&gt;Blob&lt;/a&gt; can be constructed from text, and JSON can be serialized to text, so to start we can create a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob" rel="noopener noreferrer"&gt;Blob&lt;/a&gt; from our JSON and get our stream from there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Convert JSON to Stream&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&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;Blob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;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;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have our &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream" rel="noopener noreferrer"&gt;&lt;code&gt;ReadableStream&lt;/code&gt;&lt;/a&gt;, we can use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/pipeThrough" rel="noopener noreferrer"&gt;&lt;code&gt;ReadableStream.pipeThrough&lt;/code&gt;&lt;/a&gt; method to pipe our data through the &lt;code&gt;gzip&lt;/code&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/CompressionStream" rel="noopener noreferrer"&gt;&lt;code&gt;CompressionStream&lt;/code&gt;&lt;/a&gt; transform.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// gzip stream&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;compressedReadableStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipeThrough&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CompressionStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gzip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then to get this compressed stream back to our main code, we can create a new &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Response" rel="noopener noreferrer"&gt;Response&lt;/a&gt; and handle the data like any other we would receive from the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API" rel="noopener noreferrer"&gt;Fetch API&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// create Response&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;compressedResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compressedReadableStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So what can we do with this data now it's compressed?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Well, quite a lot actually.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We could base64 encode it to a string and then store it in &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage" rel="noopener noreferrer"&gt;&lt;code&gt;localStorage&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get response Blob&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;compressedResponsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Get the ArrayBuffer&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// convert ArrayBuffer to base64 encoded string&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;compressedBase64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;btoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromCharCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Set in localStorage&lt;/span&gt;
&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;compressedData&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;compressedBase64&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could save it as a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/File/File" rel="noopener noreferrer"&gt;File&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get response Blob&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;compressedResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Create a programmatic download link&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;elem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;download&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or we could take more traditional approach, sending through HTTPS and the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API" rel="noopener noreferrer"&gt;Fetch API&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get response Blob&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;compressedResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Encoding&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gzip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And I'm sure there are many other techniques that can be used with our newly compressed JSON object. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Pretty cool, right? But how do we get our data back into a readable form in the browser?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To decompress, we first want to get our compressed data back into a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream" rel="noopener noreferrer"&gt;&lt;code&gt;ReadableStream&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Either directly from our &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob" rel="noopener noreferrer"&gt;Blob&lt;/a&gt; (or file/fetch &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Response" rel="noopener noreferrer"&gt;Response&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;compressedResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or decode back from base64.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// base64 encoding to Blob&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&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;Blob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compressedBase64&lt;/span&gt;&lt;span class="p"&gt;)],&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="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit: The b64 decode function I'm using looks like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&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;ArrayBuffer&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;binary_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&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;len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;binary_string&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bytes&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;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;len&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;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="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;len&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="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bytes&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;binary_string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&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="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;bytes&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;Then again, this time with our compressed  &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream" rel="noopener noreferrer"&gt;&lt;code&gt;ReadableStream&lt;/code&gt;&lt;/a&gt;, we go back through the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/pipeThrough" rel="noopener noreferrer"&gt;&lt;code&gt;ReadableStream.pipeThrough&lt;/code&gt;&lt;/a&gt; method, piping our data through the &lt;code&gt;gzip&lt;/code&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/DecompressionStream" rel="noopener noreferrer"&gt;&lt;code&gt;DecompressionStream&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;compressedReadableStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipeThrough&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DecompressionStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gzip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then go back into a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Response" rel="noopener noreferrer"&gt;Response&lt;/a&gt;, take the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob" rel="noopener noreferrer"&gt;Blob&lt;/a&gt; and call the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob/text" rel="noopener noreferrer"&gt;&lt;code&gt;Blob.text()&lt;/code&gt;&lt;/a&gt; method to get the content back in string format.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compressedReadableStream&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;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All to do now is parse this string back to JSON and we have our decompressed object, back in Javascript and ready to use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Demo and Results
&lt;/h2&gt;

&lt;p&gt;I've put together a small &lt;a href="https://codesandbox.io/s/relaxed-chaplygin-mc6238" rel="noopener noreferrer"&gt;CodeSandbox&lt;/a&gt; that takes a few online JSON sources and runs them through the &lt;code&gt;gzip&lt;/code&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/CompressionStream" rel="noopener noreferrer"&gt;&lt;code&gt;CompressionStream&lt;/code&gt;&lt;/a&gt; transform described in the solution above.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/relaxed-chaplygin-mc6238"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Oh, and yes. ❤️ VueJS.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Firstly, I had a blast writing this piece and exploring the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Compression_Streams_API" rel="noopener noreferrer"&gt;Compression Streams API&lt;/a&gt;. It's simple to use and feels like a really nice addition to the Javascript ecosystem.&lt;/p&gt;

&lt;p&gt;In terms of how useful the API itself is, it has &lt;strong&gt;solved a real problem&lt;/strong&gt; for me in being able to reduce my persistent data footprint.&lt;/p&gt;

&lt;p&gt;I have &lt;em&gt;already&lt;/em&gt; built this into a small feature in my app, but I do plan to integrate it deeper and bake it into the core functionality soon. Which should be another interesting problem to solve as the app has integrated client-side encryption using &lt;a href="https://github.com/FiloSottile/age" rel="noopener noreferrer"&gt;Age&lt;/a&gt; (&lt;a href="https://github.com/str4d/rage" rel="noopener noreferrer"&gt;rage&lt;/a&gt; (&lt;a href="https://github.com/kanru/rage-wasm" rel="noopener noreferrer"&gt;rage-wasm&lt;/a&gt;)). But that's for another day...&lt;/p&gt;

&lt;p&gt;It's a little disappointing that this API isn't fully supported yet, at only 72.53% support coverage, according to &lt;a href="https://caniuse.com/mdn-api_compressionstream_compressionstream_gzip" rel="noopener noreferrer"&gt;CanIUse&lt;/a&gt;. But I still think it's very worthwhile. We can implement this to fail gracefully where the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Compression_Streams_API" rel="noopener noreferrer"&gt;Compression Streams API&lt;/a&gt; is not supported and forgo the performance boost, so that's not a deal breaker for me.&lt;/p&gt;

&lt;p&gt;I'm not sure how often the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Compression_Streams_API" rel="noopener noreferrer"&gt;Compression Streams API&lt;/a&gt; will be a tool I reach for, but I'm glad to have learnt it and I'm sure this won't be the last time I use it, or at the very least consider it.&lt;/p&gt;

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