<?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: Dencio</title>
    <description>The latest articles on DEV Community by Dencio (@akosidencio).</description>
    <link>https://dev.to/akosidencio</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%2F3238382%2Fc1293f33-333e-44e0-aec0-85c2a3506cf5.jpg</url>
      <title>DEV Community: Dencio</title>
      <link>https://dev.to/akosidencio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/akosidencio"/>
    <language>en</language>
    <item>
      <title>Introducing Recached: Cache Locally, Sync Everywhere</title>
      <dc:creator>Dencio</dc:creator>
      <pubDate>Sun, 25 Oct 2026 12:06:08 +0000</pubDate>
      <link>https://dev.to/akosidencio/introducing-recached-cache-locally-sync-everywhere-19n3</link>
      <guid>https://dev.to/akosidencio/introducing-recached-cache-locally-sync-everywhere-19n3</guid>
      <description>&lt;p&gt;Imagine a dashboard showing inventory levels. Your backend has the latest value, but the browser has an older copy. Another tab needs the same update. A mobile app goes offline and reconnects later.&lt;/p&gt;

&lt;p&gt;A seemingly simple cache now involves several moving parts: server-side caching, API requests, client-side state, invalidation, WebSocket events, and reconnection logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if the cache could live inside the application—and synchronize with the server?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the idea behind &lt;strong&gt;&lt;a href="https://github.com/recached-sh/recached" rel="noopener noreferrer"&gt;Recached&lt;/a&gt;&lt;/strong&gt;, an open-source, local-first cache and real-time sync engine powered by Rust.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cache locally. Sync everywhere.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Recached is designed to keep shared cache data close to where it's used: inside a browser, a native mobile app, or a backend service. Applications can read cached values from local memory while changes flow between the server and connected clients.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: caching doesn't end at the backend
&lt;/h2&gt;

&lt;p&gt;A traditional cache such as Redis is great for keeping frequently accessed data close to your server. But if that data also needs to appear in an interface, you often end up building another system around it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your backend writes a value to the cache.&lt;/li&gt;
&lt;li&gt;Your frontend polls an endpoint or receives a custom WebSocket event.&lt;/li&gt;
&lt;li&gt;Client-side code updates another cache or state store.&lt;/li&gt;
&lt;li&gt;When a device reconnects, you decide what it missed and how to recover.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these steps is inherently wrong. But they're recurring infrastructure tasks, especially in real-time dashboards, shared application state, and offline-capable applications.&lt;/p&gt;

&lt;p&gt;Recached combines the cache and its synchronization path. The backend can continue using a familiar Redis client, while supported clients maintain a local view of the keys they watch.&lt;/p&gt;

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

&lt;p&gt;At its core, Recached uses a shared Rust cache engine across its server and client runtimes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Your backend / API
                       |
              Redis client (RESP)
                       |
                       v
               +----------------+
               | Recached       |
               | server         |
               +----------------+
                       |
                 WebSocket sync
                       |
           +-----------+-----------+
           |           |           |
           v           v           v
       Browser      Mobile      Rust service
       (WASM)       (native)     (embedded)
           |           |           |
       Local cache Local cache Local cache
           |           |           |
       Local reads  Local reads  Local reads
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two important paths are different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend writes:&lt;/strong&gt; Existing Redis clients communicate with the Recached server using its supported RESP command subset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client reads:&lt;/strong&gt; Browsers, mobile apps, and embedded Rust services read from their own local cache. Watched server data arrives through WebSocket synchronization.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A client can subscribe to key patterns to obtain an initial snapshot and later changes. Reconnecting clients can reconcile missed updates. &lt;strong&gt;An initial read may still miss until the first snapshot has arrived&lt;/strong&gt;, so applications should handle loading and synchronization state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's build a tiny live inventory example
&lt;/h2&gt;

&lt;p&gt;Here's a local development setup with a Node.js backend and a browser client.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Start the Recached server
&lt;/h3&gt;

&lt;p&gt;Run it with Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 6379:6379 &lt;span class="nt"&gt;-p&lt;/span&gt; 6380:6380 ghcr.io/recached-sh/recached:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, &lt;strong&gt;6379&lt;/strong&gt; is the Redis-compatible RESP port and &lt;strong&gt;6380&lt;/strong&gt; is the WebSocket sync port.&lt;/p&gt;

&lt;p&gt;You can verify the server responds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;redis-cli ping
&lt;span class="c"&gt;# PONG&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Security note:&lt;/strong&gt; This is a localhost development example. Do not expose a default server publicly: authentication, TLS, browser-origin restrictions, and key-level sync scopes need to be configured for your deployment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Write a value using a Redis client
&lt;/h3&gt;

&lt;p&gt;Install &lt;code&gt;ioredis&lt;/code&gt; in your Node.js project:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Then use the familiar Redis API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Redis&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;ioredis&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;cache&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;Redis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redis://127.0.0.1:6379&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;cache&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inventory:item:99&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;42&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend doesn't need a custom Recached driver. It uses the supported commands through a standard Redis client.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Read it locally in the browser
&lt;/h3&gt;

&lt;p&gt;Install the browser SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;recached-edge@^0.4.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recached runs its cache engine in the browser through WebAssembly:&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;createCache&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;recached-edge&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;cache&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;createCache&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ws://127.0.0.1:6380&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// React whenever this local key changes.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unsubscribe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onKeyChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inventory:item:99&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Inventory:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inventory:item:99&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Request an initial snapshot and subsequent updates.&lt;/span&gt;
&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;liveQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inventory:*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// This is a local-memory read; it can be null before hydration.&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inventory:item:99&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// Call unsubscribe() when the listener is no longer needed.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now change the value from your backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cache&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inventory:item:99&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;41&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The subscribed browser receives the change, updates its local cache, and invokes the change listener. The UI doesn't have to poll an API endpoint to discover the new value.&lt;/p&gt;

&lt;p&gt;The interesting part isn't merely that Recached uses WebSockets. It's that &lt;strong&gt;the value you read is already held inside the application&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  It also works without a server
&lt;/h2&gt;

&lt;p&gt;The browser package can be used as a standalone local cache. Simply omit the &lt;code&gt;connect&lt;/code&gt; option:&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;createCache&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;recached-edge&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;cache&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;createCache&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;persistence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;broadcastChannel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-app&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;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;settings&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;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;3600&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;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;settings&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;In this mode, you can use local cache operations, TTLs, JSON values, optional IndexedDB persistence, and cross-tab mutation sharing through &lt;code&gt;BroadcastChannel&lt;/code&gt;—without running a Recached server.&lt;/p&gt;

&lt;p&gt;Browser persistence has limits: it records local writes, not every server-pushed value. It's important to understand what will be available after a page refresh, especially in applications that connect to a server. The &lt;a href="https://recached.dev/browser/persistence" rel="noopener noreferrer"&gt;persistence documentation&lt;/a&gt; explains the contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  One engine, multiple runtimes
&lt;/h2&gt;

&lt;p&gt;Recached's shared Rust foundation extends beyond the browser:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Runtime&lt;/th&gt;
&lt;th&gt;Integration&lt;/th&gt;
&lt;th&gt;What it offers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Browser&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;recached-edge&lt;/code&gt; (WASM)&lt;/td&gt;
&lt;td&gt;Local cache, WebSocket sync, optional IndexedDB persistence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;React / Vue&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@recached/react&lt;/code&gt;, &lt;code&gt;@recached/vue&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Reactive adapters for the browser cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Android&lt;/td&gt;
&lt;td&gt;Kotlin SDK&lt;/td&gt;
&lt;td&gt;Native Rust core, SQLite persistence, Kotlin &lt;code&gt;Flow&lt;/code&gt; observers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iOS / macOS&lt;/td&gt;
&lt;td&gt;Swift SDK&lt;/td&gt;
&lt;td&gt;Native Rust core, SQLite persistence, Swift async observation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rust services&lt;/td&gt;
&lt;td&gt;&lt;code&gt;recached-embed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;In-process cache for watched server data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The APIs and durability guarantees aren't identical across platforms. The Android and Apple SDKs are &lt;strong&gt;0.1.0 previews&lt;/strong&gt;, the browser sync layer is &lt;strong&gt;beta&lt;/strong&gt;, and the embedded Rust client is currently an &lt;strong&gt;unpublished Git dependency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, a Rust service can keep frequently read feature flags or configuration values in its own process memory instead of fetching them over the network on every request. A native mobile app can restore cached data from SQLite after restarting offline and reconcile when it reconnects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it fits—and where it doesn't
&lt;/h2&gt;

&lt;p&gt;Recached is useful when you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live dashboards:&lt;/strong&gt; Counters, status changes, and operational data that should update without polling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared client-visible state:&lt;/strong&gt; A cache that multiple tabs or devices can observe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline-capable applications:&lt;/strong&gt; Previously saved reference data and queued cache writes that can reconcile after reconnecting, subject to documented limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local configuration reads:&lt;/strong&gt; Feature flags and tenant settings used frequently by application services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But it's important to be specific about what Recached is &lt;strong&gt;not&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's not a replacement for PostgreSQL or MySQL.&lt;/strong&gt; Your database should remain the source of truth. Publish committed state into Recached as a derived cache; a cache write is not a database transaction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's not a drop-in replacement for every Redis workload.&lt;/strong&gt; Recached supports a subset of Redis commands, but does not implement features such as Lua scripting or Redis Streams. Software that depends on those primitives may not work, even though its Redis client can connect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's not a general-purpose CRDT engine.&lt;/strong&gt; Conflicting writes do not get automatic, application-aware conflict resolution, and queued writes are not a durable business transaction system.&lt;/p&gt;

&lt;p&gt;This distinction matters. Recached is designed around &lt;strong&gt;local cache reads plus real-time synchronization&lt;/strong&gt;, not maximum Redis compatibility or database replication.&lt;/p&gt;

&lt;h2&gt;
  
  
  A critical production consideration: sync scopes
&lt;/h2&gt;

&lt;p&gt;Real-time synchronization also creates an authorization problem. In the default unscoped configuration, server mutations can be pushed to every connected WebSocket client. That's not appropriate for a multi-user application.&lt;/p&gt;

&lt;p&gt;Recached provides &lt;strong&gt;sync scopes&lt;/strong&gt; so your backend can mint signed tokens granting read or read/write access to specific key patterns. A user might receive read access to &lt;code&gt;catalog:*&lt;/code&gt; and read/write access only to their own cart keys.&lt;/p&gt;

&lt;p&gt;Watching a pattern is &lt;strong&gt;not&lt;/strong&gt; the same thing as having permission to see it. Before building a multi-user app, read the &lt;a href="https://recached.dev/server/sync-scopes" rel="noopener noreferrer"&gt;sync scopes guide&lt;/a&gt; and &lt;a href="https://recached.dev/server/security" rel="noopener noreferrer"&gt;security documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What stage is the project at?
&lt;/h2&gt;

&lt;p&gt;Recached is open source under the &lt;strong&gt;Apache 2.0 license&lt;/strong&gt;, and it's still evolving.&lt;/p&gt;

&lt;p&gt;The cache server is described as a &lt;strong&gt;release candidate for cache workloads&lt;/strong&gt;, while the synchronization layer is &lt;strong&gt;beta&lt;/strong&gt; and the native SDKs are early previews. The project has automated tests and documented performance measurements, but has not completed broad production validation or an independent security audit.&lt;/p&gt;

&lt;p&gt;The goal isn't to claim that every application should replace its current cache. It's to explore a different developer experience: &lt;strong&gt;write cached data once, keep useful copies near applications, and synchronize changes without rebuilding the same plumbing in every project&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try Recached
&lt;/h2&gt;

&lt;p&gt;If you're building a real-time web app, an offline-capable mobile experience, or a service that repeatedly reads shared configuration, Recached may be worth experimenting with.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/recached-sh/recached" rel="noopener noreferrer"&gt;github.com/recached-sh/recached&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation:&lt;/strong&gt; &lt;a href="https://recached.dev" rel="noopener noreferrer"&gt;recached.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quick start:&lt;/strong&gt; &lt;a href="https://recached.dev/guide/quick-start" rel="noopener noreferrer"&gt;recached.dev/guide/quick-start&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client support and limitations:&lt;/strong&gt; &lt;a href="https://recached.dev/guide/client-support" rel="noopener noreferrer"&gt;recached.dev/guide/client-support&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd love feedback from developers working on caching, local-first software, WebAssembly, or real-time synchronization. What would you want from a cache that runs inside both your server and your applications?&lt;/p&gt;

</description>
      <category>rust</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Meet Kepta: Remember What Happened to the Things You Own</title>
      <dc:creator>Dencio</dc:creator>
      <pubDate>Wed, 14 Oct 2026 04:14:17 +0000</pubDate>
      <link>https://dev.to/akosidencio/meet-kepta-remember-what-happened-to-the-things-you-own-4nb7</link>
      <guid>https://dev.to/akosidencio/meet-kepta-remember-what-happened-to-the-things-you-own-4nb7</guid>
      <description>&lt;p&gt;You lend someone a drill. A few weeks later, you need it and cannot remember who has it.&lt;/p&gt;

&lt;p&gt;You get your bike repaired. Months later, it starts making a noise, and you wonder what the shop replaced last time.&lt;/p&gt;

&lt;p&gt;You put your passport somewhere safe. Now you need to remember where “safe” was.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kepta gives those details a place to live.&lt;/strong&gt; It is a private Android app for keeping track of the things you own and what happens to them, so you can find the answer when you need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write it down while you remember
&lt;/h2&gt;

&lt;p&gt;Capturing an everyday detail should take a moment. In Kepta, you can start with a sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Changed the chain on the bike yesterday, paid 850 pesos at CycleHouse.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you have added your bike and CycleHouse, Kepta can connect the sentence to them and suggest the event, date, and amount. You review what it understood, make any corrections, and save.&lt;/p&gt;

&lt;p&gt;Tell Kepta that you call your Trek FX 3 “bike,” and you can use that name when writing about it.&lt;/p&gt;

&lt;p&gt;The next time you need the repair date or the cost, you have a record to return to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the history with the thing
&lt;/h2&gt;

&lt;p&gt;A laptop, bicycle, camera, or appliance can accumulate a lot of details over the years. Kepta brings them together on that thing's timeline.&lt;/p&gt;

&lt;p&gt;Keep track of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repairs and maintenance:&lt;/strong&gt; what happened, when, who did it, and what you paid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loans and returns:&lt;/strong&gt; who borrowed something and whether it came back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage and moves:&lt;/strong&gt; where you put something, with a record you can check later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Purchase and warranty details:&lt;/strong&gt; the price, serial number, model, and warranty expiry.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also attach photos of receipts, serial plates, or damage to a record. When you need to explain a problem to a repair shop, the history and the evidence are together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find the answer when it matters
&lt;/h2&gt;

&lt;p&gt;Some details are useful only months after you save them.&lt;/p&gt;

&lt;p&gt;Search for a thing or a phrase to find your records. For supported questions about loans and locations, Kepta can give you an answer with the saved memory behind it.&lt;/p&gt;

&lt;p&gt;Ask “who has my bike,” and it can show the borrower and the loan record. Look up where something is kept, and you can check the memory that placed it there.&lt;/p&gt;

&lt;p&gt;For repairs and maintenance, open the thing's timeline to see what happened last time. You have your own history to work from when deciding what to do next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let your records remind you
&lt;/h2&gt;

&lt;p&gt;A warranty date is easy to forget until something breaks. A borrowed item is easy to lose track of until you need it.&lt;/p&gt;

&lt;p&gt;Kepta offers reminders based on the details you have already recorded, including approaching warranty expiries and outstanding loans. Accept a reminder, and your phone handles the notification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep your everyday life private
&lt;/h2&gt;

&lt;p&gt;Your records can reveal plenty about your life: what you own, who you know, and where you keep things.&lt;/p&gt;

&lt;p&gt;Kepta keeps those records on your phone. It works entirely offline, with no account to create, no analytics, and no advertising. The app distributed on Google Play has no internet permission.&lt;/p&gt;

&lt;p&gt;Your saved records and attached photos are encrypted. Unlock with your PIN or optional fingerprint unlock, and Kepta locks again when you leave the app.&lt;/p&gt;

&lt;p&gt;You can use it in airplane mode, in a workshop with poor reception, or wherever a detail is worth remembering.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick note for developers
&lt;/h2&gt;

&lt;p&gt;Kepta is built with React Native and TypeScript, with TalaDB as its embedded database. Capture uses local parsing rules, and search combines structured records with full-text search. Both run on the phone without sending your text to an AI service. Semantic search is planned for a future version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with one thing
&lt;/h2&gt;

&lt;p&gt;Add something you regularly repair, lend, or misplace. Save its purchase details, record its next service, or note who you lent it to.&lt;/p&gt;

&lt;p&gt;Each time something happens, add a sentence. Over time, you build a history you can turn to instead of searching through old messages and photos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://play.google.com/store/apps/details?id=dev.thinkgrid.kepta" rel="noopener noreferrer"&gt;Get Kepta on Google Play&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What is the first thing you would keep track of?&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>android</category>
      <category>privacy</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Tanod: protecting self-hosted Next.js from SSR overload</title>
      <dc:creator>Dencio</dc:creator>
      <pubDate>Thu, 08 Oct 2026 08:03:03 +0000</pubDate>
      <link>https://dev.to/akosidencio/tanod-protecting-self-hosted-nextjs-from-ssr-overload-28gm</link>
      <guid>https://dev.to/akosidencio/tanod-protecting-self-hosted-nextjs-from-ssr-overload-28gm</guid>
      <description>&lt;p&gt;A cache can absorb a burst of requests for one popular product page. But a crawler visiting thousands of different product pages creates a different problem: every URL may need its own render.&lt;/p&gt;

&lt;p&gt;Your application can be busy generating responses that nobody else will reuse. Meanwhile, ordinary visitors are competing for the same rendering capacity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/akosidencio/tanod" rel="noopener noreferrer"&gt;Tanod&lt;/a&gt; is an open-source Rust reverse proxy for controlling origin work in server-side rendering (SSR) and other expensive HTTP applications. It combines concurrency limits, bounded queues, request coalescing, and short-lived caching, with a dedicated integration for self-hosted Next.js.&lt;/p&gt;

&lt;p&gt;Its goal is to stop traffic spikes from becoming render spikes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with counting requests alone
&lt;/h2&gt;

&lt;p&gt;Two HTTP requests can place different demands on your server. One returns a static file. Another renders a page, queries a database, and calls a content management system.&lt;/p&gt;

&lt;p&gt;A requests-per-second limit does not express how much of that work may be active at once. Longer responses can leave the origin with more concurrent work even when the arrival rate stays the same.&lt;/p&gt;

&lt;p&gt;For an illustrative steady-state workload, 40 requests per second with a 500 ms response time means about 20 requests in flight. If response time grows to 1.5 seconds at the same arrival rate, that becomes about 60. Those numbers describe the relationship, not a benchmark or a recommended server capacity.&lt;/p&gt;

&lt;p&gt;Caching and client rate limits still matter. They address different parts of the problem:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;What it controls&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Client rate limiting&lt;/td&gt;
&lt;td&gt;How much traffic a client may send over time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response caching&lt;/td&gt;
&lt;td&gt;How much repeated work can be avoided&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request coalescing&lt;/td&gt;
&lt;td&gt;How many identical concurrent requests need separate origin responses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Origin admission control&lt;/td&gt;
&lt;td&gt;How much new origin work may run at once&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Tanod concentrates on the last three. You still need an appropriate public edge and client rate limiting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Tanod does
&lt;/h2&gt;

&lt;p&gt;Tanod runs in front of your application server, also called the origin. Place it behind a content delivery network (CDN), an edge proxy, or a load balancer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser / crawler
      ↓
CDN / Caddy / NGINX / load balancer
      ↓
Tanod
      ↓
Next.js or another HTTP application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The edge handles responsibilities such as certificate management and client rate limits. Tanod handles origin capacity and eligible response reuse.&lt;/p&gt;

&lt;p&gt;Tanod is built on &lt;a href="https://github.com/cloudflare/pingora" rel="noopener noreferrer"&gt;Pingora&lt;/a&gt;, Cloudflare's Rust proxy framework. Its application-specific layer adds route classification, response-sharing policy, and bounded origin admission.&lt;/p&gt;

&lt;p&gt;The name comes from the Tagalog &lt;em&gt;tanod&lt;/em&gt;: a barangay watchman who keeps order at the gate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reuse first, then spend origin capacity
&lt;/h2&gt;

&lt;p&gt;The ordering matters because a cache hit should not wait for capacity that it does not need.&lt;/p&gt;

&lt;p&gt;For governed requests, Tanod follows this sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Match the route and classify the request.&lt;/li&gt;
&lt;li&gt;Reuse an eligible cached response or attach to a safely shareable in-flight response.&lt;/li&gt;
&lt;li&gt;Acquire the configured route and global capacity permits for remaining origin work.&lt;/li&gt;
&lt;li&gt;Wait within a bounded queue if capacity is busy.&lt;/li&gt;
&lt;li&gt;Forward admitted work, or reject it when the queue is full or its deadline expires.&lt;/li&gt;
&lt;li&gt;Check the origin response before sharing or storing it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Request coalescing means equivalent concurrent requests can receive one origin response. Microcaching means an eligible response is stored briefly so later requests can reuse it.&lt;/p&gt;

&lt;p&gt;Neither helps when every request is a distinct, uncacheable miss. The concurrency ceiling still applies to that work, including private dynamic requests.&lt;/p&gt;

&lt;p&gt;You configure the limits and optional route weights. Tanod does not automatically measure the CPU or memory cost of a render, and it cannot infer your origin's safe capacity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overload needs a defined response
&lt;/h2&gt;

&lt;p&gt;A queue is useful only when it has an end. Allowing unlimited requests to wait can move the overload from the application into the proxy.&lt;/p&gt;

&lt;p&gt;Tanod bounds both queue size and waiting time. When it cannot admit a request, the default response is &lt;code&gt;503 Service Unavailable&lt;/code&gt; with a &lt;code&gt;Retry-After&lt;/code&gt; header.&lt;/p&gt;

&lt;p&gt;For a browser loading a document, Tanod can return a configurable busy page that refreshes automatically. API clients and Next.js payload requests receive the error status without that HTML page.&lt;/p&gt;

&lt;p&gt;The busy page is a retry mechanism, not a reserved place in line. Each refresh is a new request.&lt;/p&gt;

&lt;p&gt;Eligible stale content can be served during background revalidation or an upstream failure. It is &lt;strong&gt;not currently served as a fallback for overload shedding&lt;/strong&gt;. That distinction matters when choosing how a public page should behave under load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Response sharing requires a privacy decision
&lt;/h2&gt;

&lt;p&gt;A popular URL is not necessarily a public response. A product page might include account information, a customer-specific price, or a session cookie.&lt;/p&gt;

&lt;p&gt;Tanod checks both request eligibility and response shareability. Its rules include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requests with &lt;code&gt;Authorization&lt;/code&gt; and unsafe methods bypass sharing&lt;/li&gt;
&lt;li&gt;Cookie-bearing requests are private by default&lt;/li&gt;
&lt;li&gt;Responses containing &lt;code&gt;Set-Cookie&lt;/code&gt; cannot be shared through a route override&lt;/li&gt;
&lt;li&gt;Unsupported response &lt;code&gt;Vary&lt;/code&gt; headers prevent sharing&lt;/li&gt;
&lt;li&gt;Origin &lt;code&gt;private&lt;/code&gt;, &lt;code&gt;no-cache&lt;/code&gt;, and &lt;code&gt;no-store&lt;/code&gt; directives are respected unless a validated public route explicitly overrides them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A public-route override is an application privacy assertion. It requires reviewing what the route returns, including for visitors who send cookies. It does not make a personalized response safe to share.&lt;/p&gt;

&lt;p&gt;This is why a first deployment can enable origin protection while leaving caching and coalescing disabled. You can review public routes separately after measuring the origin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next.js needs more than a URL-based cache key
&lt;/h2&gt;

&lt;p&gt;With Next.js, the same URL can return an HTML document or a React Server Components (RSC) payload. Prefetch requests introduce additional variants, while Server Actions and draft mode need their own handling.&lt;/p&gt;

&lt;p&gt;Tanod's Next.js adapter accounts for those distinctions. Cache keys separate relevant framework variants, and Server Actions and draft-mode content bypass response sharing.&lt;/p&gt;

&lt;p&gt;The companion &lt;a href="https://github.com/akosidencio/tanod/tree/main/packages/tanod-next" rel="noopener noreferrer"&gt;&lt;code&gt;@tanod/next&lt;/code&gt; package&lt;/a&gt; reads production build manifests to generate route configuration. Dynamic pages and Route Handlers remain private unless you approve them through a checked-in policy. Prerendered routes can receive public policies based on build evidence.&lt;/p&gt;

&lt;p&gt;There is also an invalidation boundary to understand: calling Next.js &lt;code&gt;revalidateTag()&lt;/code&gt; or &lt;code&gt;revalidatePath()&lt;/code&gt; alone does not invalidate a response held by Tanod. The package provides integration for coordinating both caches.&lt;/p&gt;

&lt;p&gt;Tanod can proxy other HTTP applications with manual route configuration. Next.js currently has the dedicated build integration; equivalent integrations for other frameworks remain future work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try origin protection locally
&lt;/h2&gt;

&lt;p&gt;This walkthrough starts with sharing disabled. It requires Linux x64, a Rust toolchain, and a Next.js application you can build and run locally. Tanod declares Rust 1.88 as its minimum; the repository pins its build toolchain in &lt;code&gt;rust-toolchain.toml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In a terminal inside your Next.js project, build and start the origin on loopback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
npx next start &lt;span class="nt"&gt;--hostname&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;--port&lt;/span&gt; 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a second terminal, clone Tanod and build its binary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/akosidencio/tanod.git
&lt;span class="nb"&gt;cd &lt;/span&gt;tanod
cargo build &lt;span class="nt"&gt;--release&lt;/span&gt; &lt;span class="nt"&gt;--locked&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate a local configuration that forwards to your running application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./target/release/tanod init &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--config&lt;/span&gt; tanod.local.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--upstream&lt;/span&gt; 127.0.0.1:3000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--listen&lt;/span&gt; 127.0.0.1:8080 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--concurrency&lt;/span&gt; 16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated configuration admits up to 16 concurrent origin work units and allows up to 32 queued requests, with a two-second queue deadline. Its private catch-all disables caching and coalescing.&lt;/p&gt;

&lt;p&gt;The value &lt;code&gt;16&lt;/code&gt; is a starting point for experimentation, not a recommendation for your application. Measure representative requests, origin memory use, latency, and queue waits before choosing a deployment limit.&lt;/p&gt;

&lt;p&gt;Validate the configuration, then start the proxy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./target/release/tanod check &lt;span class="nt"&gt;--config&lt;/span&gt; tanod.local.yaml
./target/release/tanod run &lt;span class="nt"&gt;--config&lt;/span&gt; tanod.local.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In another terminal, send a request through Tanod and inspect its separate admin listener:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; http://127.0.0.1:8080/
curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; http://127.0.0.1:9091/health/live
curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; http://127.0.0.1:9091/status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Application traffic enters on port &lt;code&gt;8080&lt;/code&gt;; administrative checks use port &lt;code&gt;9091&lt;/code&gt;. Keep the admin listener private in a public deployment.&lt;/p&gt;

&lt;p&gt;For a server installation with systemd and a public domain, follow the &lt;a href="https://github.com/akosidencio/tanod/blob/main/docs/STANDALONE.md" rel="noopener noreferrer"&gt;standalone guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the case where caching cannot help
&lt;/h2&gt;

&lt;p&gt;The repository includes a focused admission-control demonstration. From the Tanod directory, run it separately from your application traffic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./bench/demo.sh 60 1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script starts a fixture origin and sends 60 concurrent requests for 60 distinct URLs. Each fixture response simulates one second of work. The benchmark configuration sets the origin ceiling to 10 and disables caching.&lt;/p&gt;

&lt;p&gt;It compares direct traffic with traffic through Tanod. The origin reports its own peak concurrency, and the script asserts that the direct run exceeds the configured ceiling while the governed run stays within it.&lt;/p&gt;

&lt;p&gt;The fixture sleeps rather than performing a real React render. This checks admission behavior, not Next.js throughput. It also demonstrates the trade-off: serializing work into smaller groups can take longer when the origin could have handled the entire batch.&lt;/p&gt;

&lt;p&gt;The repository has separate &lt;a href="https://github.com/akosidencio/tanod/blob/main/docs/NEXTJS-PRODUCTION-REFERENCE.md" rel="noopener noreferrer"&gt;Next.js HTTP, browser, and replica checks&lt;/a&gt;. Those exercise framework behavior, response isolation, invalidation, and deployment boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to measure before tuning a limit
&lt;/h2&gt;

&lt;p&gt;The useful question is how your origin behaves as concurrent work grows. Request totals alone cannot answer it.&lt;/p&gt;

&lt;p&gt;Tanod exports Prometheus metrics, structured logs, and optional OpenTelemetry data. A &lt;a href="https://github.com/akosidencio/tanod/tree/main/ops" rel="noopener noreferrer"&gt;Grafana dashboard and alerting rules&lt;/a&gt; help inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Origin work in flight compared with the configured ceiling&lt;/li&gt;
&lt;li&gt;Queue waiting time and total request duration&lt;/li&gt;
&lt;li&gt;Responses shed by Tanod&lt;/li&gt;
&lt;li&gt;Origin latency and backend health&lt;/li&gt;
&lt;li&gt;Cache reuse and memory occupancy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Raising a concurrency ceiling can reduce queueing or increase overload. Check the origin's behavior before treating either outcome as an improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current limits and the next steps
&lt;/h2&gt;

&lt;p&gt;The version covered here is &lt;strong&gt;0.3.0&lt;/strong&gt;, and Tanod remains pre-1.0. Repository tests and bounded staging tests support its mechanisms, but sustained production validation and an independent cache-safety review remain open.&lt;/p&gt;

&lt;p&gt;There are deployment boundaries to account for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cache and coalescing are local to each process&lt;/li&gt;
&lt;li&gt;Several proxies sharing one origin pool need statically partitioned capacity budgets&lt;/li&gt;
&lt;li&gt;Purges must reach every instance that can hold a cached response&lt;/li&gt;
&lt;li&gt;Exact-path purges do not expand dynamic route patterns&lt;/li&gt;
&lt;li&gt;Slow readers can keep origin permits occupied; optional bounded spooling sacrifices progressive rendering to release capacity earlier&lt;/li&gt;
&lt;li&gt;Static and long-lived streaming classes do not use render permits; WebSocket upgrades have separate limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tanod can also supervise one local application through &lt;code&gt;origin.command&lt;/code&gt;, allowing the app and proxy to run within one container or service. That simplifies the process arrangement, but each instance still owns its own cache and capacity boundary.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/akosidencio/tanod/blob/main/docs/ROADMAP.md" rel="noopener noreferrer"&gt;roadmap&lt;/a&gt; focuses on deployment validation, independent review, overload behavior, and additional integrations. Tanod aims to make origin overload controlled and observable; it does not promise that every application becomes faster or cheaper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try Tanod with a workload you understand
&lt;/h2&gt;

&lt;p&gt;If you operate an expensive self-hosted origin, start by measuring concurrent work and response latency. Try Tanod with private defaults, set a capacity budget, and inspect what happens when that budget is reached. Enable response reuse only after reviewing the routes that can safely share it.&lt;/p&gt;

&lt;p&gt;The project is available under the Apache 2.0 license:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/akosidencio/tanod" rel="noopener noreferrer"&gt;Source code and README&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/akosidencio/tanod/blob/main/docs/OPERATIONS.md" rel="noopener noreferrer"&gt;Operations guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/akosidencio/tanod/blob/main/docs/THREAT-MODEL.md" rel="noopener noreferrer"&gt;Threat model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/akosidencio/tanod/issues" rel="noopener noreferrer"&gt;Issues and feedback&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feedback on self-hosting workloads, reproducible failures, and cache-safety assumptions can help determine the next priorities. When reporting behavior, include a redacted configuration, the request pattern, and the Tanod version.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>nextjs</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Your realtime UI can lie to you, and nothing logs it</title>
      <dc:creator>Dencio</dc:creator>
      <pubDate>Tue, 18 Aug 2026 10:31:58 +0000</pubDate>
      <link>https://dev.to/akosidencio/your-realtime-ui-can-lie-to-you-and-nothing-logs-it-5gbo</link>
      <guid>https://dev.to/akosidencio/your-realtime-ui-can-lie-to-you-and-nothing-logs-it-5gbo</guid>
      <description>&lt;p&gt;Most web apps poll. A client asks the server every few seconds whether anything changed, and almost every time the answer is no. A dashboard on a five-second interval costs &lt;strong&gt;720 requests an hour, per tab&lt;/strong&gt;, to mostly learn nothing — and it's &lt;em&gt;still&lt;/em&gt; seconds behind reality.&lt;/p&gt;

&lt;p&gt;So you go looking for push. And you find that every option charges a tax that has very little to do with pushing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tax
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Standalone hubs&lt;/strong&gt; — Mercure, Centrifugo — are a second service to deploy, monitor, and keep alive. Worse: that service has never seen your user table. Because it can't answer &lt;em&gt;"may this person read this?"&lt;/em&gt;, you have to build a whole authorization subsystem before one message flows. Minting tokens, scoping them to topics, expiry, rotation, revocation. That subsystem is usually the largest part of the integration, and it exists &lt;em&gt;only&lt;/em&gt; because the hub lives outside your application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sync engines&lt;/strong&gt; — ElectricSQL, PowerSync, Zero — are genuinely excellent, and they solve a much bigger problem than the one you have. They replace your data layer rather than augmenting it: a client-side database, a replication protocol, conflict resolution, a migration story. If you want offline-first local writes, use one. If you just wanted the number on the screen to be current, you've adopted a new architecture to get it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hand-rolled SSE&lt;/strong&gt; is fifteen lines that work perfectly on your laptop and fail in production for reasons nobody on the team remembers a month later:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;compression middleware buffers the stream, so nothing arrives until the connection ends&lt;/li&gt;
&lt;li&gt;a proxy reaps the connection as idle, and it silently stops delivering&lt;/li&gt;
&lt;li&gt;subscribers leak on disconnect, one per tab that ever connected&lt;/li&gt;
&lt;li&gt;updates go missing across reconnects, with &lt;strong&gt;nothing reporting it&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is the reason this post exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode nobody alerts on
&lt;/h2&gt;

&lt;p&gt;A client showing stale data &lt;em&gt;forever&lt;/em&gt; is worse than one that polls, because &lt;strong&gt;nothing fails&lt;/strong&gt;. No error is logged. No retry fires. No alert goes off. The page just quietly lies, and the first you hear about it is a support ticket saying the numbers look wrong.&lt;/p&gt;

&lt;p&gt;Every other failure in your stack announces itself. This one doesn't. And once you start looking for it, you find it's the default behaviour of almost every naive push implementation: the socket drops, it reconnects, it resumes from &lt;em&gt;somewhere&lt;/em&gt;, and nobody checks whether "somewhere" was far enough back.&lt;/p&gt;

&lt;p&gt;That gap — between what the server accepted and what the client actually received — is the thing worth engineering around.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the gap loud
&lt;/h2&gt;

&lt;p&gt;I've been building &lt;a href="https://github.com/thinkgrid-labs/aghoz" rel="noopener noreferrer"&gt;&lt;strong&gt;aghoz&lt;/strong&gt;&lt;/a&gt; (&lt;em&gt;AH-gohz&lt;/em&gt;, from the Filipino &lt;em&gt;agos&lt;/em&gt;, "flow") around exactly that idea. It's a small, dependency-free event-stream library that mounts &lt;strong&gt;into the app you already have&lt;/strong&gt; as a route.&lt;/p&gt;

&lt;p&gt;Two things follow from mounting in-process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, authorization is inherited rather than invented.&lt;/strong&gt; By the time the handler runs, your session middleware has already established who the user is. So the answer to "may this user read this topic?" is a function call against a request you already parsed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;        &lt;span class="c1"&gt;// already there&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;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loadUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;         &lt;span class="c1"&gt;// already there — sets req.user&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/events&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;topic&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;topic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`org/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orgId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one line replaces the entire token subsystem a standalone hub requires. No token exchange, no CORS when the UI and API share an origin, no second service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, an interrupted stream can fail loudly.&lt;/strong&gt; Two loss conditions are detected and reported through a single callback:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;history-truncated&lt;/code&gt; — the client reconnected with a cursor older than retained history&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;slow-consumer&lt;/code&gt; — the client couldn't drain its socket and was disconnected rather than left to starve, quietly diverging
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AghozProvider&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/events"&lt;/span&gt; &lt;span class="na"&gt;onGap&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invalidateQueries&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wire that one prop to a refetch and a client stops trusting a stream the server &lt;em&gt;knows&lt;/em&gt; is incomplete. On the client, the migration is about as small as it gets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- const { data } = useQuery({
-   queryKey: ['revenue'], queryFn: fetchRevenue,
-   refetchInterval: 5000,          // 720 requests per hour, per tab
- })
&lt;/span&gt;&lt;span class="gi"&gt;+ const data = useTopic(`org/${orgId}/revenue`, initial)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Already on TanStack Query? Keep it. Delete the interval and let the stream invalidate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useTopicInvalidation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`org/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;orgId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/orders`&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;orders&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;h2&gt;
  
  
  Two bugs that prove the point
&lt;/h2&gt;

&lt;p&gt;Here's the part I actually want to share, because it's the most useful thing I learned: &lt;strong&gt;I shipped this exact bug, twice, in the library whose entire purpose is to prevent it.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug one: the empty ring
&lt;/h3&gt;

&lt;p&gt;Truncation was computed the obvious way — &lt;code&gt;truncated = cursor &amp;lt; oldest_retained_event&lt;/code&gt;. That's wrong in &lt;em&gt;both&lt;/em&gt; directions.&lt;/p&gt;

&lt;p&gt;The false positive: &lt;code&gt;0-0&lt;/code&gt; is the cursor handed out before anything is published, and it sorts below every real id. So on a freshly booted hub, the very first connect replayed everything correctly &lt;strong&gt;and&lt;/strong&gt; reported a gap that couldn't possibly have happened. Every first page load after a deploy refetched. A signal that fires when nothing is wrong is a signal people learn to ignore.&lt;/p&gt;

&lt;p&gt;The false negative was worse. An event larger than the entire history budget gets evicted by the push that stored it, leaving the ring &lt;strong&gt;empty&lt;/strong&gt;. With no oldest retained entry, there was nothing to compare against — so the guard short-circuited and a real loss was reported as &lt;em&gt;"nothing missed."&lt;/em&gt; Silent staleness, reachable from one oversized publish.&lt;/p&gt;

&lt;p&gt;The fix is to track the &lt;strong&gt;highest id ever evicted&lt;/strong&gt; instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;truncated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;last_trimmed&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;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;last_trimmed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Over-reporting is a false alarm. Under-reporting is data loss with no symptom. Those are not equally bad, and the rule should be shaped by which one you'd rather have.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug two: the restart
&lt;/h3&gt;

&lt;p&gt;Then, while building persistent history as a performance item, I probed the behaviour it was meant to improve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;life 1: publish …872-0, publish …873-0, process dies
life 2: client reconnects with last-event-id: …872-0
        →  last-event-id-checkpoint: …872-0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An echo. The hub told a resuming client &lt;strong&gt;it had missed nothing&lt;/strong&gt;, while the event published just before shutdown was gone for good. On every restart of every deployment. Since v0.1.&lt;/p&gt;

&lt;p&gt;The cause: the previous rule asks &lt;em&gt;"did I drop something you hadn't seen?"&lt;/em&gt; — and a restarted hub has dropped nothing, &lt;strong&gt;because it remembers nothing&lt;/strong&gt;. An empty ring and a fresh install are indistinguishable from the inside.&lt;/p&gt;

&lt;p&gt;A cursor is also unvouchable from the other end — &lt;em&gt;newer than every id the hub has ever issued&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;truncated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;evicted&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;hub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two lines. A hub that has never issued an id that high cannot know what came after it.&lt;/p&gt;

&lt;p&gt;One existing test had asserted the &lt;em&gt;opposite&lt;/em&gt; — &lt;em&gt;"an empty hub cannot report truncated, there is nothing to have lost."&lt;/em&gt; That intuition is wrong, and it's exactly what hid the bug: an empty hub having nothing doesn't mean the client lost nothing. It means &lt;strong&gt;the hub cannot tell&lt;/strong&gt;. And "cannot tell" must resolve to "refetch," never to "you're fine."&lt;/p&gt;

&lt;p&gt;Both bugs existed &lt;em&gt;identically&lt;/em&gt; in two independent implementations. That's what convinced me the rules needed to live in one place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Rust — and not for the reason you'd guess
&lt;/h2&gt;

&lt;p&gt;Not for speed. I measured it: a Rust implementation of the hot path, compiled to wasm and called from Node, was &lt;strong&gt;slower than plain JavaScript&lt;/strong&gt; at realistic payload sizes. The Rust logic is genuinely 2.2x faster, but marshalling strings across the boundary gives all of it back and then some — 0.65x at 2 KB payloads. The measurement overturned the plan.&lt;/p&gt;

&lt;p&gt;The real reason is &lt;strong&gt;one implementation of the rules that must never differ&lt;/strong&gt;: id assignment (including when the clock moves backwards), topic validation, frame encoding, and the atomicity that makes the checkpoint honest.&lt;/p&gt;

&lt;p&gt;This isn't hypothetical. The very first run of the conformance corpus caught a real divergence: the JavaScript hub measured topic length in &lt;strong&gt;UTF-16 code units&lt;/strong&gt; while Rust measured &lt;strong&gt;UTF-8 bytes&lt;/strong&gt;. An 86-character Japanese topic is 258 bytes — rejected by one, accepted by the other, on the exact field whose validation exists to prevent forged frames. Every language has its own wrong answer for "length."&lt;/p&gt;

&lt;p&gt;So the protocol is pinned by two language-neutral corpora: &lt;strong&gt;97 vectors&lt;/strong&gt; for the protocol core and &lt;strong&gt;42 scenarios&lt;/strong&gt; for the HTTP layer over a real socket. The truncation bugs above each became corpus vectors, verified to fail against the old code first — because fixing it in two places without a vector just leaves the next implementation free to reintroduce it.&lt;/p&gt;

&lt;p&gt;The honest costs: a C ABI can't be written without &lt;code&gt;unsafe&lt;/code&gt;, so there are 33 unsafe sites in one auditable file, verified in CI by Miri. The protocol logic itself carries &lt;code&gt;#![forbid(unsafe_code)]&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it deliberately doesn't do
&lt;/h2&gt;

&lt;p&gt;Scope is a feature, so the exclusions come before the install line:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It will not work on serverless.&lt;/strong&gt; Vercel, Lambda, Cloudflare Workers can't hold a long-lived connection. No workaround, none planned.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is not a sync engine.&lt;/strong&gt; No offline support, no local writes, no CRDTs, no client-side database. Writes go through the API you already have; the stream is one-way, permanently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is not bidirectional.&lt;/strong&gt; If you need that, use Socket.IO.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple processes need a backplane.&lt;/strong&gt; &lt;code&gt;@aghoz/redis&lt;/code&gt; adds Redis Streams; without it there are zero dependencies at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need offline-first local writes, use a real sync engine — you'll be happier than with a bad imitation. aghoz is for the case where the server already knows something and the browser should stop asking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;v0.4.2&lt;/code&gt; is on npm. Node 22+, Express / Fastify / NestJS on the server, React / Vue / Svelte in the browser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add @aghoz/server @aghoz/client @aghoz/react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's early — the API may still change and there's no deprecation policy yet, which is stated at the top of the README rather than discovered later. The protocol is written down, every significant decision is recorded with its evidence (including the three that got reversed), and the example app runs end to end in CI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/akosidencio/aghoz" rel="noopener noreferrer"&gt;github.com/akosidencio/aghozghoz&lt;/a&gt; · MIT OR Apache-2.0&lt;/p&gt;

&lt;p&gt;If you've hit the silent-staleness failure in your own stack, I'd genuinely like to hear how you found it. In my experience it's never an alert. It's always a support ticket.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>node</category>
      <category>rust</category>
    </item>
    <item>
      <title>Your Coding Agent Can Read the Code—but Can It See the App Fail?</title>
      <dc:creator>Dencio</dc:creator>
      <pubDate>Mon, 17 Aug 2026 14:52:44 +0000</pubDate>
      <link>https://dev.to/akosidencio/your-coding-agent-can-read-the-code-but-can-it-see-the-app-fail-49dc</link>
      <guid>https://dev.to/akosidencio/your-coding-agent-can-read-the-code-but-can-it-see-the-app-fail-49dc</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;TailFlow gives coding agents compact, queryable evidence from the applications&lt;br&gt;
they are changing—without sending local logs to a hosted platform.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Coding agents are increasingly capable of navigating repositories, editing&lt;br&gt;
multiple files, running tests, and explaining unfamiliar systems.&lt;/p&gt;

&lt;p&gt;But there is still a gap in the typical agent workflow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The agent can read the code, but it often cannot see what happens after the&lt;br&gt;
application starts.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A build may pass while the development server fails during startup. A frontend&lt;br&gt;
may compile but crash during hot reload. A background worker may begin retrying&lt;br&gt;
indefinitely. A Docker container may restart with a configuration error.&lt;/p&gt;

&lt;p&gt;If the agent cannot observe that output, the workflow usually becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agent edits code
→ checks pass
→ application fails at runtime
→ developer notices the terminal error
→ developer copies the error back to the agent
→ agent tries again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That manual handoff is the problem&lt;br&gt;
&lt;a href="https://github.com/thinkgrid-labs/tailflow" rel="noopener noreferrer"&gt;TailFlow&lt;/a&gt; is designed to solve.&lt;/p&gt;
&lt;h2&gt;
  
  
  Runtime verification for coding agents
&lt;/h2&gt;

&lt;p&gt;TailFlow is an open-source, local runtime-verification layer for coding agents.&lt;/p&gt;

&lt;p&gt;It collects output from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Development processes&lt;/li&gt;
&lt;li&gt;Local Docker containers&lt;/li&gt;
&lt;li&gt;Log files&lt;/li&gt;
&lt;li&gt;Piped standard input&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;TailFlow then exposes the same bounded runtime view through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCP tools for coding agents&lt;/li&gt;
&lt;li&gt;A shell CLI for scripts and terminal-based agents&lt;/li&gt;
&lt;li&gt;An interactive terminal UI&lt;/li&gt;
&lt;li&gt;A local web dashboard&lt;/li&gt;
&lt;li&gt;An HTTP API and Server-Sent Events stream&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not simply to display logs. The goal is to let an agent answer&lt;br&gt;
concrete questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the expected services actually start?&lt;/li&gt;
&lt;li&gt;Did my edit trigger a successful rebuild?&lt;/li&gt;
&lt;li&gt;What failed after this change?&lt;/li&gt;
&lt;li&gt;Is this a new failure or an old one?&lt;/li&gt;
&lt;li&gt;Is the service healthy, or did it never start?&lt;/li&gt;
&lt;li&gt;Is this one error repeated hundreds of times?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The resulting loop looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;capture the stack
→ establish a baseline
→ make the change
→ wait for the runtime outcome
→ inspect failures
→ verify the fix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why tests are not enough
&lt;/h2&gt;

&lt;p&gt;Tests remain essential, but they prove only what they exercise.&lt;/p&gt;

&lt;p&gt;A passing test suite does not necessarily prove that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Environment variables are available&lt;/li&gt;
&lt;li&gt;Ports can be bound&lt;/li&gt;
&lt;li&gt;Containers can communicate&lt;/li&gt;
&lt;li&gt;A development server completed startup&lt;/li&gt;
&lt;li&gt;Hot reload succeeded&lt;/li&gt;
&lt;li&gt;A background job finished&lt;/li&gt;
&lt;li&gt;A real request followed the expected path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Runtime output contains evidence that static analysis and isolated tests cannot&lt;br&gt;
provide. TailFlow makes that evidence accessible to the agent without requiring&lt;br&gt;
the developer to continually watch several terminal tabs.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting started in about five minutes
&lt;/h2&gt;

&lt;p&gt;Install TailFlow through npm:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;This installs four commands:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tailflow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Interactive TUI and project initializer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tailflow-daemon&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runtime collector and local API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tailflow-mcp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MCP bridge for coding agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tailflow-logs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shell client for queries and automation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;From the root of a project, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailflow init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TailFlow detects common runtime sources, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;serve&lt;/code&gt;, and &lt;code&gt;start&lt;/code&gt; scripts in &lt;code&gt;package.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;pnpm, Yarn, Bun, and npm project metadata&lt;/li&gt;
&lt;li&gt;Docker Compose files&lt;/li&gt;
&lt;li&gt;Common local log directories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It then proposes a configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TailFlow v0.3.2

TailFlow found:

  1. process web: pnpm run dev [recommended]
  2. Docker containers (compose.yml) [recommended]
  3. file worker: logs/worker.log [recommended]

Select sources:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After selection, TailFlow writes a &lt;code&gt;tailflow.toml&lt;/code&gt; file. Existing configurations&lt;br&gt;
are never replaced unless &lt;code&gt;--force&lt;/code&gt; is explicitly provided.&lt;/p&gt;

&lt;p&gt;For noninteractive environments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailflow init &lt;span class="nt"&gt;--yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also specify sources directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailflow init &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--docker&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--process&lt;/span&gt; &lt;span class="s1"&gt;'api=go run ./cmd/api'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--file&lt;/span&gt; logs/worker.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the collector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailflow-daemon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The local dashboard becomes available at&lt;br&gt;
&lt;a href="http://127.0.0.1:7878" rel="noopener noreferrer"&gt;&lt;code&gt;http://127.0.0.1:7878&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Verify the connection from another terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailflow-logs status
tailflow-logs sources
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Connecting a coding agent
&lt;/h2&gt;

&lt;p&gt;For Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add tailflow &lt;span class="nt"&gt;--&lt;/span&gt; tailflow-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For another MCP-compatible client:&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;"mcpServers"&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;"tailflow"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tailflow-mcp"&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;The MCP server gives the agent four focused tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;list_log_sources&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Shows which sources are running, exited, failed, or merely observed.&lt;/p&gt;

&lt;p&gt;This distinction matters. An empty error list does not prove that a service is&lt;br&gt;
healthy—it may never have started.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;get_recent_errors&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Returns distinct recent failures with occurrence counts and related stack&lt;br&gt;
context.&lt;/p&gt;

&lt;p&gt;Instead of filling the agent's context window with the same crash 400 times,&lt;br&gt;
TailFlow can condense it into one failure group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x400 connection refused: postgres:5432
     at Pool.connect (...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;search_logs&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Returns exact records with source, severity, time, regular-expression, and&lt;br&gt;
cursor filters. This is useful when exact values or event ordering matter more&lt;br&gt;
than deduplication.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;wait_for_logs&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Waits inside the daemon until a runtime event appears. An agent can wait for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;compiled successfully
server listening
migration complete
request finished
error|failed|panic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This replaces arbitrary sleep-and-poll loops with event-driven verification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separating new failures from old failures
&lt;/h2&gt;

&lt;p&gt;One of TailFlow's most important features is its cursor model.&lt;/p&gt;

&lt;p&gt;Every captured record receives a monotonically increasing sequence number. The&lt;br&gt;
agent can save the current cursor before making a change and request only&lt;br&gt;
records that appeared afterward.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;baseline cursor: 241
        │
        ├── edit application code
        ├── hot reload begins
        └── wait after cursor 241
              ├── compilation succeeded
              └── server ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This changes the question from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Are there errors in the logs?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What happened after this specific edit?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;TailFlow also reports when the requested cursor has fallen outside its bounded&lt;br&gt;
buffer. That prevents an agent from presenting incomplete evidence as proof&lt;br&gt;
that nothing failed.&lt;/p&gt;
&lt;h2&gt;
  
  
  One runtime model for developers and agents
&lt;/h2&gt;

&lt;p&gt;TailFlow deliberately gives humans and agents access to the same underlying&lt;br&gt;
data.&lt;/p&gt;

&lt;p&gt;Developers can use the terminal UI:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Or inspect Docker directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailflow &lt;span class="nt"&gt;--docker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shell-based agents and scripts can query the daemon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailflow-logs errors &lt;span class="nt"&gt;--since&lt;/span&gt; 5m
tailflow-logs search &lt;span class="s1"&gt;'timeout'&lt;/span&gt; &lt;span class="nt"&gt;--source&lt;/span&gt; api
tailflow-logs &lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="nt"&gt;--grep&lt;/span&gt; &lt;span class="s1"&gt;'compiled successfully|Failed to compile'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The web dashboard provides live following, severity filters, source counts, and&lt;br&gt;
regular-expression search.&lt;/p&gt;

&lt;p&gt;This shared model makes agent behavior easier to audit: the developer can&lt;br&gt;
inspect the same runtime evidence the agent used to reach its conclusion.&lt;/p&gt;
&lt;h2&gt;
  
  
  TailFlow is intentionally local
&lt;/h2&gt;

&lt;p&gt;TailFlow is not trying to replace production observability platforms.&lt;/p&gt;

&lt;p&gt;It does not provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hosted log storage&lt;/li&gt;
&lt;li&gt;Distributed tracing&lt;/li&gt;
&lt;li&gt;Metrics or profiling&lt;/li&gt;
&lt;li&gt;Production alerting&lt;/li&gt;
&lt;li&gt;Multi-tenant access control&lt;/li&gt;
&lt;li&gt;Autonomous service remediation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, it focuses on one job:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Give a coding agent timely, compact evidence from the local software it is&lt;br&gt;
changing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The daemon binds to loopback, stores a bounded in-memory buffer, and does not&lt;br&gt;
require an account or hosted service.&lt;/p&gt;

&lt;p&gt;That makes it useful during development, but it also creates important&lt;br&gt;
limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;History disappears when the daemon restarts&lt;/li&gt;
&lt;li&gt;High-volume stacks can evict older records&lt;/li&gt;
&lt;li&gt;Severity detection and error grouping are heuristic&lt;/li&gt;
&lt;li&gt;Docker discovery currently follows all local containers&lt;/li&gt;
&lt;li&gt;Logs may contain secrets, and TailFlow does not redact them&lt;/li&gt;
&lt;li&gt;The local API should not be exposed directly to a network&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These boundaries are documented rather than hidden behind a generic “healthy”&lt;br&gt;
result.&lt;/p&gt;
&lt;h2&gt;
  
  
  What arrived in TailFlow 0.3.2
&lt;/h2&gt;

&lt;p&gt;Version 0.3.2 focuses on reducing setup friction and making the project easier&lt;br&gt;
to understand and operate.&lt;/p&gt;

&lt;p&gt;The release includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Guided project initialization with &lt;code&gt;tailflow init&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Automatic discovery of development scripts, Compose files, and log files&lt;/li&gt;
&lt;li&gt;Package-manager-aware generated commands&lt;/li&gt;
&lt;li&gt;Safe interactive and noninteractive configuration&lt;/li&gt;
&lt;li&gt;Explicit process, file, and Docker source flags&lt;/li&gt;
&lt;li&gt;Version banners across the TUI, dashboard, initializer, daemon, and MCP startup&lt;/li&gt;
&lt;li&gt;Reworked documentation organized around the runtime-verification problem&lt;/li&gt;
&lt;li&gt;Clearer limitations, architecture guidance, feature framing, and roadmap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The broader direction is to make runtime verification a normal step in an agent&lt;br&gt;
coding loop—not a manual debugging step performed only after the agent declares&lt;br&gt;
success.&lt;/p&gt;
&lt;h2&gt;
  
  
  What comes next
&lt;/h2&gt;

&lt;p&gt;Planned directions include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Letting the MCP bridge optionally start a local daemon&lt;/li&gt;
&lt;li&gt;Serving MCP directly from the daemon over streamable HTTP&lt;/li&gt;
&lt;li&gt;Comparing failure groups before and after an edit&lt;/li&gt;
&lt;li&gt;Better startup and readiness signals&lt;/li&gt;
&lt;li&gt;Compose-aware container selection&lt;/li&gt;
&lt;li&gt;Optional bounded local persistence&lt;/li&gt;
&lt;li&gt;Correlation across services using request or job identifiers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;TailFlow will remain local-first, bounded for agent context, and explicit about&lt;br&gt;
incomplete evidence.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try TailFlow
&lt;/h2&gt;

&lt;p&gt;TailFlow is open source and licensed under MIT.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; tailflow
&lt;span class="nb"&gt;cd &lt;/span&gt;your-project
tailflow init
tailflow-daemon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then connect your coding agent or explore the local dashboard at&lt;br&gt;
&lt;a href="http://127.0.0.1:7878" rel="noopener noreferrer"&gt;&lt;code&gt;http://127.0.0.1:7878&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Project links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/thinkgrid-labs/tailflow" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/tailflow" rel="noopener noreferrer"&gt;npm package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/thinkgrid-labs/tailflow#documentation" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/thinkgrid-labs/tailflow/blob/main/docs/roadmap.md" rel="noopener noreferrer"&gt;Roadmap&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your coding agent has ever produced a change that looked correct while the&lt;br&gt;
application was visibly failing in another terminal, TailFlow is built for that&lt;br&gt;
missing part of the loop.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>rust</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Checkgate: Feature Flags That Don't Cost You a Network Round-Trip</title>
      <dc:creator>Dencio</dc:creator>
      <pubDate>Wed, 22 Jul 2026 01:57:02 +0000</pubDate>
      <link>https://dev.to/akosidencio/checkgate-feature-flags-that-dont-cost-you-a-network-round-trip-4d7j</link>
      <guid>https://dev.to/akosidencio/checkgate-feature-flags-that-dont-cost-you-a-network-round-trip-4d7j</guid>
      <description>&lt;p&gt;An open-source, self-hosted feature flag engine that evaluates flags in-process (sub-microsecond) and syncs every SDK over SSE in under 50ms. Built in Rust, with native SDKs for Node, Web, React Native, and Flutter.&lt;/p&gt;

&lt;p&gt;Every feature-flag SaaS I've used shares the same architecture: your code calls &lt;code&gt;isEnabled()&lt;/code&gt;, that call goes over the network to their servers, and you wait 5–50ms for an answer. It's fast enough that nobody complains in a demo, and slow enough that it shows up the moment you put a flag check in a hot path, an edge function, or a mobile app on a bad connection.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;&lt;a href="https://github.com/ThinkGrid-Labs/checkgate" rel="noopener noreferrer"&gt;Checkgate&lt;/a&gt;&lt;/strong&gt; — a self-hosted, open-source feature flag engine that flips the model: flags are evaluated &lt;strong&gt;in-process&lt;/strong&gt;, in local memory, with &lt;strong&gt;no network call on the hot path&lt;/strong&gt;. The server's only job is to push changes to every connected SDK the moment they happen.&lt;/p&gt;

&lt;p&gt;![A flag change flows from the dashboard to the Checkgate server, which pushes it over SSE to every SDK, where flags evaluate locally in-process.]&lt;br&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuvalnye3gv8gs27lv7ku.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuvalnye3gv8gs27lv7ku.png" alt=" " width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The core idea
&lt;/h2&gt;

&lt;p&gt;Most flag services treat evaluation as a remote procedure call. Checkgate treats it as a local cache lookup that happens to stay in sync in real time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your app boots and the SDK opens one persistent SSE connection to the Checkgate server.&lt;/li&gt;
&lt;li&gt;The server bootstraps it with the full flag set for that environment.&lt;/li&gt;
&lt;li&gt;From then on, every &lt;code&gt;isEnabled()&lt;/code&gt; is a pure, in-memory function call — no I/O, no allocations on the hot path.&lt;/li&gt;
&lt;li&gt;When someone flips a flag in the dashboard, the server pushes the delta over SSE, and every connected SDK instance is updated within &lt;strong&gt;~50ms&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────┐          SSE push, &amp;lt;50ms          ┌─────────┐
│  Checkgate Server     │ ───────────────────────────────▶ │ Node SDK │
│  (Rust + Postgres +   │ ───────────────────────────────▶ │ Web SDK  │
│   Redis pub/sub)      │ ───────────────────────────────▶ │ RN SDK   │
└──────────────────────┘                                    └─────────┘
                                                          isEnabled() = ~100ns, local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The evaluation core (&lt;code&gt;checkgate-core&lt;/code&gt;) is a Rust library compiled to different targets depending on the platform:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Binding&lt;/th&gt;
&lt;th&gt;Artifact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Node.js&lt;/td&gt;
&lt;td&gt;NAPI-RS&lt;/td&gt;
&lt;td&gt;native &lt;code&gt;.node&lt;/code&gt; addon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser&lt;/td&gt;
&lt;td&gt;wasm-bindgen&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.wasm&lt;/code&gt; + JS glue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;React Native&lt;/td&gt;
&lt;td&gt;JSI (C FFI)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.so&lt;/code&gt; / &lt;code&gt;.dylib&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flutter&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dart:ffi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.so&lt;/code&gt; / &lt;code&gt;.dylib&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same Rust logic everywhere. No re-implemented rule engine per platform, no drift between what your Node backend decides and what your mobile app decides for the same user.&lt;/p&gt;
&lt;h2&gt;
  
  
  It's not just a boolean toggle store
&lt;/h2&gt;

&lt;p&gt;A flag in Checkgate can be a boolean, string, integer, or JSON value, and evaluation runs through a well-defined pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;isEnabled(flag_key, user_key, attributes)
    │
    ├── Prerequisites unsatisfied  → false  (checked first, fails closed)
    ├── Flag not found             → false
    ├── flag.is_enabled == false   → false
    │
    ├── Targeting rules (first match wins)
    │   └── rule.attribute ∈ attributes AND operator matches → true
    │
    └── Rollout percentage
        ├── 0%   → false
        ├── 100% → true
        └── MurmurHash3(flag_key + ":" + user_key) % 100 &amp;lt; pct → true/false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things that fall out of that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Targeting rules&lt;/strong&gt; (&lt;code&gt;equals&lt;/code&gt;, &lt;code&gt;contains&lt;/code&gt;, &lt;code&gt;starts_with&lt;/code&gt;, &lt;code&gt;greater_than&lt;/code&gt;, etc.) always win over rollout percentage — so you can ship a feature at 5% globally while your whole team sees it via an &lt;code&gt;email ends_with @yourcompany.com&lt;/code&gt; rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rollout percentage&lt;/strong&gt; uses deterministic MurmurHash3 bucketing: the same user always lands in the same bucket, so bumping 10% → 20% only adds new users, it never reshuffles the cohort you already have.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Segments&lt;/strong&gt; let you define an audience ("Internal Employees", "Beta Users") once and reference it from any flag by key — they're expanded server-side, so SDKs never need to know segments exist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prerequisite flags&lt;/strong&gt; let one flag depend on another being enabled (or resolved to a specific value), evaluated recursively with a depth limit that fails closed on cycles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weighted variants&lt;/strong&gt; turn a flag into an A/B/n split — a 60/30/10 traffic split across &lt;code&gt;control&lt;/code&gt; / &lt;code&gt;treatment-a&lt;/code&gt; / &lt;code&gt;treatment-b&lt;/code&gt; — using the same sticky hashing approach, salted independently so variant assignment doesn't correlate with the rollout gate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of that lives entirely in the flag payload the SDK already has locally. No extra round-trip for a targeting decision, ever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Governance, because "who changed this flag in prod" is a real question
&lt;/h2&gt;

&lt;p&gt;Once more than one person can touch a flag, you need more than a toggle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RBAC&lt;/strong&gt; — &lt;code&gt;admin&lt;/code&gt; / &lt;code&gt;editor&lt;/code&gt; / &lt;code&gt;viewer&lt;/code&gt;, per project, independent of workspace-level admin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change-request approvals&lt;/strong&gt; — require a second reviewer before a change takes effect in sensitive environments; self-approval is blocked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit log&lt;/strong&gt; of every change, plus &lt;strong&gt;scheduled changes&lt;/strong&gt; and a &lt;strong&gt;cross-environment diff&lt;/strong&gt; with one-click promote.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personal access tokens&lt;/strong&gt;, scoped and revocable, as an alternative to handing out admin-equivalent SDK keys to CI/CD.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack &amp;amp; Microsoft Teams alerts&lt;/strong&gt; — flag and change-request activity land in a channel with native formatting (Block Kit for Slack, MessageCard for Teams), subscribable per event type (&lt;code&gt;flag.updated&lt;/code&gt;, &lt;code&gt;change_request.rejected&lt;/code&gt;, etc.).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The ecosystem
&lt;/h2&gt;

&lt;p&gt;The server and SDKs are the core, but the parts that made Checkgate feel finished for real infrastructure are the boring-but-essential pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/ThinkGrid-Labs/checkgate/tree/main/ssr" rel="noopener noreferrer"&gt;&lt;code&gt;@checkgate/ssr&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — server-render initial flag state for Next.js/Remix/SvelteKit and hydrate with zero flag flicker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/ThinkGrid-Labs/checkgate/tree/main/edge" rel="noopener noreferrer"&gt;&lt;code&gt;@checkgate/edge&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — a zero-dependency, runtime-agnostic evaluator for Cloudflare Workers / Fly.io that pulls a flag snapshot, caches it with TTL + stale-while-revalidate, and fails open on origin outages — delegating actual evaluation to the same WASM core every other SDK uses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/ThinkGrid-Labs/checkgate/tree/main/cli" rel="noopener noreferrer"&gt;&lt;code&gt;@checkgate/cli&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — &lt;code&gt;checkgate typegen&lt;/code&gt; generates type-safe flag accessors for TypeScript, Dart, and Rust, so a typo'd flag key is a compile error instead of a silent &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terraform / OpenTofu provider&lt;/strong&gt; and a &lt;strong&gt;Kubernetes operator&lt;/strong&gt; (&lt;code&gt;FeatureFlag&lt;/code&gt; CRD) for managing flags as code, with drift correction and &lt;code&gt;require_approval&lt;/code&gt; awareness.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 ghcr.io/thinkgrid-labs/checkgate:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single command pulls the published, multi-arch (&lt;code&gt;amd64&lt;/code&gt;/&lt;code&gt;arm64&lt;/code&gt;) all-in-one image — PostgreSQL, Redis, the Checkgate server, and the dashboard bundled together, no build step, no clone. Finish the setup wizard at &lt;code&gt;localhost:3000/setup&lt;/code&gt;, grab the auto-generated SDK key, and:&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;CheckgateClient&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;@checkgate/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&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;CheckgateClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;serverUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sdkKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sk_live_your_key_here&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-checkout-flow&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;user-123&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;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alice@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client connects once, downloads the flag set, and every subsequent &lt;code&gt;isEnabled()&lt;/code&gt; is local. That's the whole pitch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why self-host this instead of paying per-seat
&lt;/h2&gt;

&lt;p&gt;If you've priced out LaunchDarkly or Statsig at scale, the pitch for a self-hosted alternative is usually cost. The pitch for Checkgate specifically is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Evaluation latency&lt;/strong&gt; goes from "network call" to "~100ns," which matters if you're flag-checking inside a request hot path, an edge worker, or a mobile app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your user attributes never leave your infra&lt;/strong&gt; — targeting attributes are evaluated locally and are never sent to the server, which matters if you have data-residency constraints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No vendor lock-in&lt;/strong&gt; — it's Apache 2.0, single Rust binary + Postgres + Redis, and you can read every line of the evaluation logic that decides what your users see.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's still early — the &lt;a href="https://github.com/ThinkGrid-Labs/checkgate" rel="noopener noreferrer"&gt;roadmap&lt;/a&gt; includes a warehouse-export path and the A/B testing significance calculations are in beta — but the core (evaluation, SSE propagation, targeting, rollouts, governance) is solid enough to run in production today.&lt;/p&gt;

&lt;p&gt;Repo: &lt;strong&gt;&lt;a href="https://github.com/checkgate-dev/checkgate" rel="noopener noreferrer"&gt;https://github.com/checkgate-dev/checkgate&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
Docs: &lt;strong&gt;&lt;a href="https://checkgate-dev.github.io/checkgate" rel="noopener noreferrer"&gt;https://checkgate-dev.github.io/checkgate&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you try it, I'd genuinely like to hear where it breaks — issues and PRs are welcome.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>featureflags</category>
    </item>
    <item>
      <title>Your app has two caches. What if it only needed one?</title>
      <dc:creator>Dencio</dc:creator>
      <pubDate>Sun, 19 Jul 2026 04:56:23 +0000</pubDate>
      <link>https://dev.to/akosidencio/your-app-has-two-caches-what-if-it-only-needed-one-3d1j</link>
      <guid>https://dev.to/akosidencio/your-app-has-two-caches-what-if-it-only-needed-one-3d1j</guid>
      <description>&lt;p&gt;Redis on the server, some state library in the browser, and a pile of hand-written sync between them. I got tired of writing that layer, so I built a cache engine that compiles to both native and WebAssembly.&lt;/p&gt;

&lt;p&gt;Every app I've worked on ends up with the same shape.&lt;/p&gt;

&lt;p&gt;There's a cache on the server — Redis, usually. And there's state in the browser — Zustand, Redux, React Query, whatever. Between them sits a layer nobody designed on purpose: &lt;code&gt;fetchedAt&lt;/code&gt; timestamps, staleness checks, an invalidation call you have to remember to make after every mutation, and a polling interval you tuned once and never revisited.&lt;/p&gt;

&lt;p&gt;That layer is where the bugs live. The cart total that's stale for four seconds. The feature flag that flipped in the dashboard but not in the tab that's already open. The counter that goes backwards because two tabs raced.&lt;/p&gt;

&lt;p&gt;The frustrating part is that both halves are &lt;em&gt;caches&lt;/em&gt;. They store keys, they expire things, they invalidate. We just built them out of completely different parts and then wrote glue to keep them agreeing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if it were literally the same cache?
&lt;/h2&gt;

&lt;p&gt;That's the question behind &lt;a href="https://github.com/thinkgrid-labs/recached" rel="noopener noreferrer"&gt;Recached&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's a cache server written in Rust that speaks RESP — the Redis wire protocol — on port 6379. Your existing client works unchanged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Redis&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;ioredis&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;cache&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;Redis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redis://127.0.0.1:6379&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;cache&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inventory:item:99&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;42&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;That part is unremarkable. Here's the part that isn't.&lt;/p&gt;

&lt;p&gt;The evaluation engine — the thing that actually stores keys, applies TTLs, and evaluates commands — is a single Rust crate with no networking and no file I/O. It compiles to native for the server &lt;strong&gt;and&lt;/strong&gt; to &lt;code&gt;wasm32&lt;/code&gt; for the browser. Same source, same semantics, both sides.&lt;/p&gt;

&lt;p&gt;A WebSocket sync layer keeps them in step. So the browser doesn't &lt;em&gt;call&lt;/em&gt; the cache. The browser &lt;strong&gt;is&lt;/strong&gt; a cache, holding a live replica.&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;createCache&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;recached-edge&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;cache&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;createCache&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;persistence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                        &lt;span class="c1"&gt;// survives refresh via IndexedDB&lt;/span&gt;
  &lt;span class="na"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ws://127.0.0.1:6380&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;// syncs with the server&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inventory:item:99&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "42" — from local WASM memory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That read doesn't touch the network. It's a lookup in a hash map that happens to be inside your browser tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  What that changes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Reads stop being async problems.&lt;/strong&gt; No loading state for data you already have. No stale-while-revalidate dance. The value is in memory or it isn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pushes replace polling.&lt;/strong&gt; A server-side write fans out over the WebSocket and lands in every connected browser's local copy. In React:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useKeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cart:item:*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// current state + live updates&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;/api/cart&lt;/code&gt; endpoint. No interval. No socket handler you wrote by hand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Offline stops being a special case.&lt;/strong&gt; Writes made while disconnected apply locally and queue in an IndexedDB-backed outbox. On reconnect the client re-authenticates, re-subscribes, and replays. Because &lt;em&gt;operations&lt;/em&gt; replay rather than final values, merges follow the data type — two clients incrementing the same counter both land, instead of one clobbering the other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tabs agree for free.&lt;/strong&gt; Cross-tab sync goes over BroadcastChannel, no server round-trip.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest part
&lt;/h2&gt;

&lt;p&gt;I'd rather you evaluate this accurately than be surprised later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The server is in good shape&lt;/strong&gt; for cache workloads: snapshots and AOF persistence, replication with automatic single-replica failover, TLS, constant-time auth, hardened parsers, and a load/chaos suite in CI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The browser sync layer is beta.&lt;/strong&gt; The invariants are specified and tested end to end, but the code is young and hasn't had a third-party security review. Concretely: don't expose the sync port to untrusted multi-tenant traffic until you've read the sync-scopes documentation and understand the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On performance&lt;/strong&gt; — pipelined, it's ahead of Redis on 6 of 7 commands, because it's multi-threaded where Redis executes on one core. Unpipelined it runs at 46–96% of Redis depending on the command; &lt;code&gt;HSET&lt;/code&gt; in particular is a known outlier I'm still working on. The full table, including every case where it loses, is published in the docs. The goal was never to beat Redis at being Redis — it's to delete the network hop for client reads, which no server-side cache can do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it isn't the right tool:&lt;/strong&gt; if nothing but your own backend reads the cache, use Redis. It's more mature, has more data structures, and solves that problem completely. Recached earns its place when a browser, mobile app, or edge worker needs the same data your backend writes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 6379:6379 &lt;span class="nt"&gt;-p&lt;/span&gt; 6380:6380 ghcr.io/recached-dev/recached:latest
npm &lt;span class="nb"&gt;install &lt;/span&gt;recached-edge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docs: &lt;a href="https://recached.dev/" rel="noopener noreferrer"&gt;recached.dev&lt;/a&gt; · Source: &lt;a href="https://github.com/recached-dev/recached" rel="noopener noreferrer"&gt;github.com/recached-dev/recached&lt;/a&gt; · Apache 2.0&lt;/p&gt;

&lt;p&gt;Also in the box: a native JSON type with path updates and RFC 7386 merge, sliding-window rate limiting as a first-class command, live queries, and primary/replica replication.&lt;/p&gt;




&lt;p&gt;If you've built that sync layer by hand — and I think most of us have — I'd genuinely like to hear which part broke for you. That's the part I most want to get right.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>webassembly</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Local-First Vectors: How to Build Privacy-Preserving AI Apps without the Cloud</title>
      <dc:creator>Dencio</dc:creator>
      <pubDate>Fri, 10 Apr 2026 11:32:37 +0000</pubDate>
      <link>https://dev.to/akosidencio/local-first-vectors-how-to-build-privacy-preserving-ai-apps-without-the-cloud-4lih</link>
      <guid>https://dev.to/akosidencio/local-first-vectors-how-to-build-privacy-preserving-ai-apps-without-the-cloud-4lih</guid>
      <description>&lt;h2&gt;
  
  
  The Missing Piece for On-Device AI
&lt;/h2&gt;

&lt;p&gt;The world of AI is moving to the edge. With the rise of on-device models like &lt;strong&gt;Transformers.js&lt;/strong&gt;, &lt;strong&gt;Gemma&lt;/strong&gt;, and &lt;strong&gt;Phind&lt;/strong&gt;, we are closer than ever to a truly "dark" application architecture—one where zero data leaves the user's device.&lt;/p&gt;

&lt;p&gt;However, there’s a paradox: while we have the models running on-device, we are still sending our sensitive data to cloud-based vector databases like Pinecone or Weaviate to perform similarity searches. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I wanted to solve this paradox.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’ve been building &lt;strong&gt;TalaDB&lt;/strong&gt;: an open-source, local-first document and vector database built in Rust that runs identically across the Browser (WASM), Node.js, and React Native.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Multi-Platform Problem
&lt;/h2&gt;

&lt;p&gt;If you've ever tried to build a cross-platform, local-first app, you know the pain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Developing for the browser?&lt;/strong&gt; You're likely stuck with IndexedDB or a complex WASM-SQL setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developing for Mobile?&lt;/strong&gt; You're probably using SQLite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer Experience (DX) Hell:&lt;/strong&gt; Managing separate drivers, binary extensions for vector search (&lt;code&gt;sqlite-vss&lt;/code&gt;), and split business logic is a nightmare.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted a single, unified API. &lt;strong&gt;One core to rule them all.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing TalaDB: A Unified Engine
&lt;/h2&gt;

&lt;p&gt;TalaDB provides a familiar, MongoDB-like API for both document filtering and vector similarity search. Whether you are in a React Native app or a Chrome SharedWorker, the code looks exactly the same:&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;results&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;articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findNearest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;embedding&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;support&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&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;One call. Metadata filter + Vector ranking. No cloud round-trips.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep Dive: Under the Hood
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why Rust + redb?
&lt;/h3&gt;

&lt;p&gt;I chose a pure-Rust architecture because of the safety and performance guarantees. For the storage engine, I use &lt;a href="https://github.com/cberner/redb" rel="noopener noreferrer"&gt;redb&lt;/a&gt;—a high-performance B-tree store that provides ACID transactions without the overhead of a full SQL engine.&lt;/p&gt;

&lt;h3&gt;
  
  
  WASM + OPFS: The Bleeding Edge
&lt;/h3&gt;

&lt;p&gt;In the browser, TalaDB leverages the &lt;strong&gt;Origin Private File System (OPFS)&lt;/strong&gt;. By running the database inside a &lt;code&gt;SharedWorker&lt;/code&gt;, I can achieve near-native performance while keeping the main UI thread completely free.&lt;/p&gt;

&lt;h3&gt;
  
  
  Binary Compactness
&lt;/h3&gt;

&lt;p&gt;By using &lt;code&gt;postcard&lt;/code&gt; for binary serialization, TalaDB keeps data footprints extremely small—often smaller and faster than traditional JSON-based stores. The entire WASM bundle is sub-400KB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Example: Offline Semantic Search
&lt;/h2&gt;

&lt;p&gt;Imagine building a support app that works 100% offline. Here is how you'd handle a hybrid query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;openDB&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;taladb&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;db&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;openDB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;docs.db&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;articles&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="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;articles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Find the 5 most relevant articles for a given embedding&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&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;articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findNearest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;embedding&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userVector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&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="nf"&gt;forEach&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;score&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Future of Local-First
&lt;/h2&gt;

&lt;p&gt;TalaDB is currently in &lt;strong&gt;Alpha (v0.3.0)&lt;/strong&gt;. My goal is to bridge the gap between human privacy and machine-learning intelligence. &lt;/p&gt;

&lt;p&gt;I’m currently focused on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⚡ Further optimizations for React Native JSI.&lt;/li&gt;
&lt;li&gt;📡 Adding atomic sync and multi-user capabilities.&lt;/li&gt;
&lt;li&gt;🧠 Expanding the query operator library.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;TalaDB is open-source and MIT licensed.&lt;/strong&gt; I’d love for you to try the alpha, give me some feedback, or even &lt;a href="https://github.com/taladb/taladb" rel="noopener noreferrer"&gt;give the project a star&lt;/a&gt; if you find it useful.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/taladb/taladb" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://taladb-playground.vercel.app/" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://taladb.dev/" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rust</category>
      <category>webassembly</category>
      <category>reactnative</category>
      <category>database</category>
    </item>
  </channel>
</rss>
