<?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>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/ThinkGrid-Labs/checkgate" rel="noopener noreferrer"&gt;https://github.com/ThinkGrid-Labs/checkgate&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
Docs: &lt;strong&gt;&lt;a href="https://thinkgrid-labs.github.io/checkgate" rel="noopener noreferrer"&gt;https://thinkgrid-labs.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/thinkgrid-labs/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/thinkgrid-labs/recached" rel="noopener noreferrer"&gt;github.com/thinkgrid-labs/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/thinkgrid-labs/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/thinkgrid-labs/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://thinkgrid-labs.github.io/taladb/" 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>
