<?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: Matias Affolter</title>
    <description>The latest articles on DEV Community by Matias Affolter (@matias_affolter).</description>
    <link>https://dev.to/matias_affolter</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%2F1810003%2Faded81b1-0a4b-4705-9cc7-6d29db0d4b0f.png</url>
      <title>DEV Community: Matias Affolter</title>
      <link>https://dev.to/matias_affolter</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/matias_affolter"/>
    <language>en</language>
    <item>
      <title>🛡️ How I Built a NSFWJS Alternative That's 2.5 Lighter (~1 MB Gzipped) and Runs in ~65 ms (WASM)</title>
      <dc:creator>Matias Affolter</dc:creator>
      <pubDate>Thu, 04 Jun 2026 14:01:28 +0000</pubDate>
      <link>https://dev.to/matias_affolter/how-i-built-a-nsfwjs-alternative-thats-25x-lighter-1-mb-gzipped-and-runs-in-65-ms-wasm-23o3</link>
      <guid>https://dev.to/matias_affolter/how-i-built-a-nsfwjs-alternative-thats-25x-lighter-1-mb-gzipped-and-runs-in-65-ms-wasm-23o3</guid>
      <description>&lt;p&gt;&lt;em&gt;A field report on shipping a &lt;a href="https://www.npmjs.com/package/@pixagram/nsfw-lite?activeTab=readme" rel="noopener noreferrer"&gt;binary SFW/NSFW image classifier&lt;/a&gt; for &lt;a href="https://pixagram.com" rel="noopener noreferrer"&gt;pixagram&lt;/a&gt; that runs **entirely in the browser&lt;/em&gt;&lt;em&gt;, tuned for the one thing every off-the-shelf model quietly gets wrong: pixel art.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 The itch
&lt;/h2&gt;

&lt;p&gt;I run a Web3 platform for &lt;strong&gt;pixel art&lt;/strong&gt;. People upload sprites, mint them, share them. And the moment you let strangers upload images, you inherit a very old problem: some of those images shouldn't be shown to everyone.&lt;/p&gt;

&lt;p&gt;The obvious move is &lt;a href="https://github.com/infinitered/nsfwjs" rel="noopener noreferrer"&gt;NSFWJS&lt;/a&gt;. It's excellent, it's battle-tested, and it runs in the browser. I tried it. Then I looked at what it was doing on &lt;em&gt;my&lt;/em&gt; content and realized I was using a sledgehammer to crack a very specific nut.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"I didn't need a model that knows a thousand things about a photograph. I needed one that knows exactly two things about a sprite."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I built my own. This is the story of how — the good decisions, the one that almost shipped a broken model, and the numbers I ended up with. I've written it so a junior dev can follow the whole thing and a senior one still finds the war stories useful. Where I use jargon, there's a &lt;strong&gt;🔰 In plain terms&lt;/strong&gt; box right after.&lt;/p&gt;




&lt;h2&gt;
  
  
  🤔 Why not just use NSFWJS?
&lt;/h2&gt;

&lt;p&gt;NSFWJS is built around a &lt;strong&gt;MobileNetV2&lt;/strong&gt; backbone, trained on a large corpus of &lt;em&gt;photographic and drawn&lt;/em&gt; web imagery, and it returns &lt;strong&gt;five&lt;/strong&gt; categories (&lt;code&gt;Drawing&lt;/code&gt;, &lt;code&gt;Hentai&lt;/code&gt;, &lt;code&gt;Neutral&lt;/code&gt;, &lt;code&gt;Porn&lt;/code&gt;, &lt;code&gt;Sexy&lt;/code&gt;). That's a fantastic general tool. But three things didn't fit me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It's photographic.&lt;/strong&gt; My content is pixel art — hard edges, tiny palettes, no anti-aliasing. A model that learned from photos has never really &lt;em&gt;seen&lt;/em&gt; a 160×160 sprite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's bigger than my whole problem.&lt;/strong&gt; Five classes and a ~3.5M-parameter backbone is a multi-megabyte download for a yes/no question.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I only ever ask one question.&lt;/strong&gt; "Is this safe to show or not?" That's a single threshold, not a five-way softmax I have to interpret.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted something I could &lt;strong&gt;embed in my bundle&lt;/strong&gt;, that ran &lt;strong&gt;on-device&lt;/strong&gt; (no upload, no server round-trip, no privacy headache), and that was trained to understand the medium I actually serve.&lt;/p&gt;




&lt;h2&gt;
  
  
  📋 What I actually needed
&lt;/h2&gt;

&lt;p&gt;Before writing a line of code, I wrote the spec. It fit on a sticky note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Binary.&lt;/strong&gt; &lt;code&gt;sfw&lt;/code&gt; vs &lt;code&gt;nsfw&lt;/code&gt;, one probability, one threshold.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Tiny.&lt;/strong&gt; Small enough to base64-embed in an npm package — ideally ~1 MB.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Fast.&lt;/strong&gt; Real-time-ish on a mid laptop without a GPU. Target: well under 100 ms.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;On-device.&lt;/strong&gt; Runs in a Web Worker via WebAssembly. Nothing leaves the browser.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Pixel-art-aware.&lt;/strong&gt; The preprocessing has to respect the pixel grid, not smear it.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Self-contained.&lt;/strong&gt; &lt;code&gt;npm install&lt;/code&gt;, import, call &lt;code&gt;classify()&lt;/code&gt;. No model-hosting step for the consumer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything below is downstream of that list.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Choosing a brain that fits in a tweet
&lt;/h2&gt;

&lt;p&gt;I picked &lt;strong&gt;MobileNetV4-conv-small-050&lt;/strong&gt; from &lt;a href="https://github.com/huggingface/pytorch-image-models" rel="noopener noreferrer"&gt;&lt;code&gt;timm&lt;/code&gt;&lt;/a&gt;. It's &lt;strong&gt;~0.96M parameters&lt;/strong&gt; — roughly &lt;strong&gt;2.5× lighter&lt;/strong&gt; than the MobileNetV2 that NSFWJS leans on — and it's one of the strongest architectures &lt;em&gt;per parameter&lt;/em&gt; you can pull off the shelf with ImageNet weights.&lt;/p&gt;

&lt;p&gt;Then I made it cheaper still: I trained and ran it at &lt;strong&gt;160×160&lt;/strong&gt; instead of the usual 224. For a convolutional net, compute scales with the pixel area, so dropping from 224 to 160 cuts the work to roughly &lt;strong&gt;half&lt;/strong&gt; — landing the model around &lt;strong&gt;~65 MFLOPs&lt;/strong&gt; per image. For a binary decision on a sprite, that resolution is plenty.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔰 &lt;strong&gt;In plain terms:&lt;/strong&gt; the "backbone" is the pretrained image-understanding part of the model. Starting from one that already learned general vision on millions of photos (ImageNet) means I only have to teach it the &lt;em&gt;last mile&lt;/em&gt; — "of the things you already see, which are NSFW?" — instead of teaching it to see from scratch.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🔧 The pipeline (the boring part that matters)
&lt;/h2&gt;

&lt;p&gt;The whole thing is four steps, and each one hands its output to the next:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Train&lt;/strong&gt; in PyTorch/&lt;code&gt;timm&lt;/code&gt; — fine-tune the backbone on a two-folder dataset (&lt;code&gt;nsfw/&lt;/code&gt;, &lt;code&gt;sfw/&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export&lt;/strong&gt; to &lt;a href="https://onnx.ai/" rel="noopener noreferrer"&gt;ONNX&lt;/a&gt; — a portable model format the browser can run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quantize&lt;/strong&gt; — shrink the weights from 32-bit floats to 8-bit integers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embed&lt;/strong&gt; — base64 the quantized model straight into the JS bundle, so there's no separate fetch.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At runtime it's &lt;a href="https://onnxruntime.ai/docs/tutorials/web/" rel="noopener noreferrer"&gt;&lt;code&gt;onnxruntime-web&lt;/code&gt;&lt;/a&gt;, which can run the same model on &lt;strong&gt;WebGPU&lt;/strong&gt; when it's available and fall back to &lt;strong&gt;WebAssembly (WASM)&lt;/strong&gt; on the CPU otherwise.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔰 &lt;strong&gt;In plain terms:&lt;/strong&gt; WASM lets the browser run compiled code at near-native speed. It's why a neural network can run client-side without melting the tab.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The thing I want to stress for anyone building one of these: &lt;strong&gt;the four steps are one chain, and the weakest link decides your accuracy.&lt;/strong&gt; I learned that the hard way twice — once on quantization, once on preprocessing.&lt;/p&gt;




&lt;h2&gt;
  
  
  🐇 The quantization rabbit hole (where I almost outsmarted myself)
&lt;/h2&gt;

&lt;p&gt;My first instinct was to go &lt;em&gt;aggressive&lt;/em&gt;: if 8-bit is good, &lt;strong&gt;4-bit (Q4)&lt;/strong&gt; must be better, right? Half the size, the blogs promise 2× speedups, everyone's quantizing LLMs to 4-bit. I almost spent a weekend on it.&lt;/p&gt;

&lt;p&gt;Then I actually checked what 4-bit means in ONNX Runtime, and on my own model. Two facts stopped me cold:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ONNX Runtime's 4-bit path is &lt;strong&gt;weight-only and only touches &lt;code&gt;MatMul&lt;/code&gt; operations&lt;/strong&gt; (it rewrites them into &lt;code&gt;MatMulNBits&lt;/code&gt;). It's a tool built for transformers.&lt;/li&gt;
&lt;li&gt;My model, when I counted its operations, is &lt;strong&gt;46 convolutions, one tiny classifier head, and &lt;em&gt;zero&lt;/em&gt; MatMuls.&lt;/strong&gt; A MobileNet is almost pure convolution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I ran the 4-bit quantizer on it anyway, just to be sure. It dutifully &lt;strong&gt;skipped every single node&lt;/strong&gt; and handed me back a file that was &lt;strong&gt;100% of the original size.&lt;/strong&gt; Nothing happened, because there was nothing it knew how to touch.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Halving a file that already fits in cache doesn't make it faster — it just makes it smaller. Those are not the same win. The 4-bit magic is real; it's just real for a different kind of model than mine."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The memory-bandwidth argument behind 4-bit assumes a &lt;em&gt;huge&lt;/em&gt; model whose weights can't fit in fast memory. Mine is ~1 MB — it lives in CPU cache comfortably. So I dropped the fantasy and used &lt;strong&gt;uint8&lt;/strong&gt;, which is exactly what ONNX Runtime recommends for the WASM/CPU path.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔰 &lt;strong&gt;In plain terms:&lt;/strong&gt; quantization stores each weight in fewer bits (32-bit float → 8-bit int) to shrink and speed up the model. Less precision, smaller file. The trick is doing it without changing the model's &lt;em&gt;answers&lt;/em&gt; — which is exactly where I got burned later.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🎨 Pixel art breaks all the rules
&lt;/h2&gt;

&lt;p&gt;Here's the thing nobody warns you about when your domain is pixel art: &lt;strong&gt;how you resize the image matters more than almost anything else.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every model needs a fixed input size, so every image gets resized. For photos, the default — &lt;strong&gt;bilinear&lt;/strong&gt; interpolation, which smoothly blends pixels — is fine. For pixel art, it's &lt;em&gt;destructive&lt;/em&gt;: it takes crisp, intentional blocks and &lt;strong&gt;blurs them into mush&lt;/strong&gt;. The thing that makes a sprite a sprite is the first thing bilinear throws away.&lt;/p&gt;

&lt;p&gt;The right filter for upscaling pixel art is &lt;strong&gt;nearest-neighbor&lt;/strong&gt; ("pixelated"): it keeps every block sharp.&lt;/p&gt;

&lt;p&gt;But here's the trap, and it's the one that bites everyone: &lt;strong&gt;the resize at training time and the resize at serving time have to be identical.&lt;/strong&gt; If you train on crisp nearest-neighbor images and then serve on blurred bilinear ones, the model is seeing a different distribution than it learned. You didn't just lose a little accuracy — you're running a different model than the one you trained.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"A model is only as honest as its preprocessing. Train on crisp pixels, serve on blurred ones, and you've quietly shipped a model you never actually tested."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I made the resize filter a &lt;strong&gt;single source of truth&lt;/strong&gt;: you choose it once at training time (&lt;code&gt;--interp nearest&lt;/code&gt;), it gets stamped into the model checkpoint, the exporter reads it back and bakes it into the preprocessing config, and the browser reads &lt;em&gt;that&lt;/em&gt; and resizes the same way. One decision, threaded through the entire chain, impossible to desync.&lt;/p&gt;

&lt;p&gt;(One sharp edge worth flagging: in the browser, the cleaner-looking &lt;code&gt;createImageBitmap&lt;/code&gt; API &lt;em&gt;can&lt;/em&gt; do the resize, but its quality setting is browser-dependent — Firefox has historically ignored the "pixelated" hint. For guaranteed nearest-neighbor everywhere, an old-fashioned &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; with image smoothing turned &lt;strong&gt;off&lt;/strong&gt; is the reliable tool.)&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚡ Making it fast (and invisible) in the browser
&lt;/h2&gt;

&lt;p&gt;A classifier that freezes the UI is a classifier nobody ships. So the runtime does a few things to stay out of the way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🧵 &lt;strong&gt;Web Worker by default.&lt;/strong&gt; Inference runs on a background thread, with an automatic fall back to the main thread where workers aren't available. Same API either way.&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;Batching.&lt;/strong&gt; Multiple &lt;code&gt;classify()&lt;/code&gt; calls within a few milliseconds get coalesced into a &lt;em&gt;single&lt;/em&gt; inference, so the fixed per-call overhead is paid once for the whole batch.&lt;/li&gt;
&lt;li&gt;🎒 &lt;strong&gt;The model travels with the code.&lt;/strong&gt; It's base64-embedded in the bundle — one fewer network request, and it works offline.&lt;/li&gt;
&lt;li&gt;🖥️ &lt;strong&gt;GPU when present, CPU when not.&lt;/strong&gt; The runtime tries WebGPU and gracefully falls back to WASM, so a uint8 model still reaches the GPU on machines that have one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result of all this — small backbone, 160px, uint8, off-thread — is an inference that lands around &lt;strong&gt;~65 ms&lt;/strong&gt; on a CPU. Fast enough to check an image the moment it's dropped in.&lt;/p&gt;




&lt;h2&gt;
  
  
  🐛 The bug that almost broke me: "it never says NSFW"
&lt;/h2&gt;

&lt;p&gt;This is the part I almost left out, because it's embarrassing. But it's also the most useful thing in this whole post, so here it is.&lt;/p&gt;

&lt;p&gt;Everything was wired up. I deployed it. And it &lt;strong&gt;never flagged anything as NSFW.&lt;/strong&gt; Not once. Clearly-NSFW pixel art sailed right through as "safe."&lt;/p&gt;

&lt;p&gt;My first assumption was the worst-case one: the model never learned, or my training data (photographic) didn't transfer to pixel art at all. A wasted model. But before re-training anything, I wrote a tiny &lt;strong&gt;sanity-check script&lt;/strong&gt; — the single most valuable hour I spent on the project. All it does is run the &lt;strong&gt;full-precision&lt;/strong&gt; model and the &lt;strong&gt;quantized&lt;/strong&gt; model on the &lt;em&gt;same image&lt;/em&gt;, with the &lt;em&gt;exact&lt;/em&gt; preprocessing, in plain Python. No browser, no canvas, no worker. Just: where, precisely, does the answer go wrong?&lt;/p&gt;

&lt;p&gt;I ran it on one NSFW sprite. Here's what it told me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nsfw_pixelart.png
  FP32 : P(nsfw)=0.973   ← the real model is CONFIDENTLY correct
  UINT8: P(nsfw)=0.106   ← the SHIPPED model is confidently WRONG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There it was. The full-precision model was &lt;strong&gt;right&lt;/strong&gt; — 97% sure it was NSFW. The quantized model I'd actually deployed was 89% sure it was &lt;em&gt;safe&lt;/em&gt;. &lt;strong&gt;Quantization had flipped the answer.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"FP32 said 0.97. The version I shipped said 0.11. The model wasn't wrong — the model I deployed was a different model than the one I trained."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The cause is a classic, and it's specific to this family of networks. MobileNets are built from &lt;strong&gt;depthwise convolutions&lt;/strong&gt;, where each channel has its own little filter and the weight magnitudes vary &lt;em&gt;wildly&lt;/em&gt; from channel to channel. I'd quantized with a single scale for each whole weight tensor (&lt;strong&gt;per-tensor&lt;/strong&gt;), which crushed the small-magnitude channels to nothing. The fix is &lt;strong&gt;per-channel&lt;/strong&gt; quantization — give every channel its own scale — which is a single flag (&lt;code&gt;--wasm-quant u8s8&lt;/code&gt;, per-channel int8 weights).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔰 &lt;strong&gt;In plain terms:&lt;/strong&gt; imagine compressing a choir by setting one volume knob for everyone. The loud singers are fine; the quiet ones vanish. Per-channel quantization gives each singer their own knob. For MobileNets, that's the difference between a working model and a broken one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The deeper lesson wasn't even about quantization. It was that &lt;strong&gt;the model you train and the model you deploy are not automatically the same model&lt;/strong&gt; — export and quantization are transformations that can silently change behavior. A 30-line script that compares them at the boundary is worth more than any amount of staring at training curves.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏆 The result
&lt;/h2&gt;

&lt;p&gt;Where it all landed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;NSFWJS (typical)&lt;/th&gt;
&lt;th&gt;This&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Backbone&lt;/td&gt;
&lt;td&gt;MobileNetV2 (~3.5M params)&lt;/td&gt;
&lt;td&gt;MobileNetV4-conv-small-050 (~0.96M)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;5 categories&lt;/td&gt;
&lt;td&gt;binary &lt;code&gt;sfw&lt;/code&gt; / &lt;code&gt;nsfw&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domain&lt;/td&gt;
&lt;td&gt;photographic / drawn&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;pixel art&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Size&lt;/td&gt;
&lt;td&gt;several MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~1 MB gzipped&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inference&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;~65 ms&lt;/strong&gt; on CPU (WASM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runs&lt;/td&gt;
&lt;td&gt;in browser&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;in browser, in a worker, offline&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Roughly &lt;strong&gt;2.5× lighter&lt;/strong&gt;, single-purpose, on-device, and — critically — trained and served with preprocessing that respects the medium. It ships as a self-contained npm package: &lt;code&gt;npm install&lt;/code&gt;, &lt;code&gt;import { classify }&lt;/code&gt;, done.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 What I'd tell my past self
&lt;/h2&gt;

&lt;p&gt;The reusable lessons, stripped of my specific stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🎯 &lt;strong&gt;Match the model to the question.&lt;/strong&gt; A binary problem doesn't need a five-class model; a sprite doesn't need a photo-scale backbone. Smaller-but-fitted beats bigger-but-generic.&lt;/li&gt;
&lt;li&gt;🧮 &lt;strong&gt;"Smaller file" and "faster" are different wins.&lt;/strong&gt; 4-bit shrinks giant models that are memory-bound. A 1-MB CNN is compute-bound and already cache-resident — different problem, different tool.&lt;/li&gt;
&lt;li&gt;🔗 &lt;strong&gt;Preprocessing is part of the model.&lt;/strong&gt; Train and serve through the &lt;em&gt;identical&lt;/em&gt; pipeline, or you're testing one thing and shipping another. Make it a single source of truth.&lt;/li&gt;
&lt;li&gt;🧪 &lt;strong&gt;Verify at every boundary.&lt;/strong&gt; Export and quantization can flip your answers. A tiny full-precision-vs-quantized diff script will save you a day of guessing.&lt;/li&gt;
&lt;li&gt;🐛 &lt;strong&gt;When it "never fires," suspect the transform, not the model.&lt;/strong&gt; My model was perfect. The 8-bit copy of it wasn't.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;This powers content moderation on my pixel-art platform, and it's open. If you're building anything that takes user images in the browser — especially in a niche domain where the big general models don't quite fit — I'd genuinely encourage you to consider training a small, fitted model instead of reaching for the default. It's less work than it sounds, and the result is something you actually understand top to bottom.&lt;/p&gt;

&lt;p&gt;Build the thing that knows exactly what it needs to know. 🎨🛡️&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Happy shipping.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Benchmarked LacertaDB &amp; PouchDB — Here's What Happened</title>
      <dc:creator>Matias Affolter</dc:creator>
      <pubDate>Thu, 26 Mar 2026 17:29:39 +0000</pubDate>
      <link>https://dev.to/matias_affolter/i-benchmarked-lacertadb-pouchdb-heres-what-happened-3l9n</link>
      <guid>https://dev.to/matias_affolter/i-benchmarked-lacertadb-pouchdb-heres-what-happened-3l9n</guid>
      <description>&lt;p&gt;LacertaDB vs PouchDB: a head-to-head performance comparison of two browser-native document databases, and why your serializer matters more than you think.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Browser Database Problem Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Every browser database eventually hits the same wall: &lt;strong&gt;serialization&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your data has to be transformed before it hits IndexedDB, and transformed back when it comes out. Most libraries use JSON. Some use CBOR or MessagePack. The choice of serializer silently determines your throughput ceiling — and most developers never question it.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://www.npmjs.com/package/@pixagram/lacerta-db" rel="noopener noreferrer"&gt;LacertaDB&lt;/a&gt; to be the fastest browser-native document database possible. Along the way, I had to build its serializer from scratch too, because nothing on npm was fast enough &lt;em&gt;or&lt;/em&gt; complete enough. Then I ran it against PouchDB — the established king of browser databases — to see where things actually stand.&lt;/p&gt;

&lt;p&gt;Here are the numbers. No cherry-picking, no synthetic micro-ops. Real document CRUD at scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Benchmark Setup
&lt;/h2&gt;

&lt;p&gt;Both databases ran identical workloads in the same browser tab, same machine, same IndexedDB backend. The test: &lt;strong&gt;5,000 documents&lt;/strong&gt; with ~200-byte payloads, covering the five operations that matter in a real app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bulk Write&lt;/strong&gt; — insert all documents in a single batch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read All&lt;/strong&gt; — retrieve every document&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query (filter)&lt;/strong&gt; — find documents matching a field condition&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bulk Update&lt;/strong&gt; — modify every document&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delete All&lt;/strong&gt; — remove everything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;LacertaDB uses &lt;code&gt;batchAdd&lt;/code&gt; / &lt;code&gt;getAll&lt;/code&gt; / &lt;code&gt;query&lt;/code&gt; / &lt;code&gt;batchUpdate&lt;/code&gt; / &lt;code&gt;clear&lt;/code&gt;.&lt;br&gt;
PouchDB uses &lt;code&gt;bulkDocs&lt;/code&gt; / &lt;code&gt;allDocs&lt;/code&gt; / &lt;code&gt;find&lt;/code&gt; (with pouchdb-find) / &lt;code&gt;bulkDocs&lt;/code&gt; / &lt;code&gt;bulkDocs&lt;/code&gt; with &lt;code&gt;_deleted&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Both are fair comparisons of each library's recommended bulk API.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;LacertaDB&lt;/th&gt;
&lt;th&gt;PouchDB 7.3&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bulk Write&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;532 ms&lt;/td&gt;
&lt;td&gt;2,664 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Read All&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;146 ms&lt;/td&gt;
&lt;td&gt;356 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.4×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Query&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;124 ms&lt;/td&gt;
&lt;td&gt;399 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.2×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bulk Update&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;101 ms&lt;/td&gt;
&lt;td&gt;2,708 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;26.8×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Delete All&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;239 ms&lt;/td&gt;
&lt;td&gt;2,573 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10.8×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The gap ranges from &lt;strong&gt;2.4× to nearly 27×&lt;/strong&gt;, depending on the operation. Writes and deletes show the biggest difference — and those are precisely the operations where serialization overhead dominates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Throughput in Context
&lt;/h3&gt;

&lt;p&gt;At 5,000 documents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LacertaDB sustained &lt;strong&gt;~9,400 writes/sec&lt;/strong&gt; and &lt;strong&gt;~34,000 reads/sec&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;PouchDB managed &lt;strong&gt;~1,900 writes/sec&lt;/strong&gt; and &lt;strong&gt;~14,600 reads/sec&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't a marginal difference. For offline-first apps syncing hundreds of records, or Web3 dApps caching blockchain state locally, the gap between "feels instant" and "shows a spinner" lives right in this range.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the Gap? It's the Serializer.
&lt;/h2&gt;

&lt;p&gt;PouchDB stores documents as JSON. That's fine for simple objects, but JSON has well-known limitations: no &lt;code&gt;Date&lt;/code&gt;, no &lt;code&gt;undefined&lt;/code&gt;, no &lt;code&gt;Map&lt;/code&gt;, no &lt;code&gt;Set&lt;/code&gt;, no &lt;code&gt;RegExp&lt;/code&gt;, no typed arrays, no binary data. And JSON.stringify/parse, while native C++ under the hood, still has to traverse every property, escape every string, and produce a text representation that's larger than the source data.&lt;/p&gt;

&lt;p&gt;LacertaDB uses &lt;a href="https://www.npmjs.com/package/@pixagram/turboserial" rel="noopener noreferrer"&gt;TurboSerial&lt;/a&gt;, a binary serializer I built specifically for this problem. The design goals were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Serialize everything JavaScript can hold&lt;/strong&gt; — not just JSON-safe types&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Produce smaller output&lt;/strong&gt; — binary, no delimiters, no escaping&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be faster than the alternatives&lt;/strong&gt; — including CBOR and MessagePack&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  TurboSerial vs MessagePack vs CBOR
&lt;/h3&gt;

&lt;p&gt;I ran TurboSerial against the two established binary serialization formats. The benchmark measures both throughput (ops/sec) and output size across three payload profiles:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test Case&lt;/th&gt;
&lt;th&gt;MessagePack&lt;/th&gt;
&lt;th&gt;CBOR&lt;/th&gt;
&lt;th&gt;TurboSerial&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Small Data&lt;/strong&gt; (API response)&lt;/td&gt;
&lt;td&gt;20,101 ops/s · 34B&lt;/td&gt;
&lt;td&gt;112,360 ops/s · 36B&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;176,991 ops/s&lt;/strong&gt; · 62B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Medium Data&lt;/strong&gt; (array of objects)&lt;/td&gt;
&lt;td&gt;334 ops/s · 4,093B&lt;/td&gt;
&lt;td&gt;3,460 ops/s · 4,168B&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;5,587 ops/s&lt;/strong&gt; · 4,809B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Large Data&lt;/strong&gt; (TypedArray 0.2MB)&lt;/td&gt;
&lt;td&gt;711 ops/s · 200,005B&lt;/td&gt;
&lt;td&gt;341 ops/s · 200,005B&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;2,703 ops/s&lt;/strong&gt; · 200,023B&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;TurboSerial is consistently &lt;strong&gt;1.5–8× faster&lt;/strong&gt; than both MessagePack and CBOR across payload sizes. The output is slightly larger on small payloads (it encodes richer type metadata), but the throughput advantage more than compensates — especially at the medium and large data sizes that matter in a database context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Type Coverage: Where JSON, CBOR, and TurboSerial Diverge
&lt;/h3&gt;

&lt;p&gt;This is the part that rarely makes it into benchmark posts, but it's what actually matters when you're building a real app. Here's what each format can serialize natively:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;JSON&lt;/th&gt;
&lt;th&gt;MessagePack&lt;/th&gt;
&lt;th&gt;CBOR&lt;/th&gt;
&lt;th&gt;TurboSerial&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Strings, Numbers, Booleans, null&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nested Objects / Arrays&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;undefined&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Date&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ (tag)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Map&lt;/code&gt; / &lt;code&gt;Set&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RegExp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BigInt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ (tag)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ArrayBuffer&lt;/code&gt; / TypedArrays&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Int8Array&lt;/code&gt; through &lt;code&gt;Float64Array&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Error&lt;/code&gt; objects&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sparse Arrays&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;NaN&lt;/code&gt;, &lt;code&gt;Infinity&lt;/code&gt;, &lt;code&gt;-Infinity&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;-0&lt;/code&gt; (negative zero)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;JSON covers about 7 types. CBOR and MessagePack stretch to ~12 with tags and binary extensions. TurboSerial natively handles &lt;strong&gt;20+ JavaScript types&lt;/strong&gt; — including the edge cases that silently break your data when you use JSON (like &lt;code&gt;undefined&lt;/code&gt; being stripped from objects, or &lt;code&gt;Date&lt;/code&gt; becoming a string you have to manually parse back).&lt;/p&gt;

&lt;p&gt;When your database serializer supports &lt;code&gt;Map&lt;/code&gt;, &lt;code&gt;Set&lt;/code&gt;, &lt;code&gt;RegExp&lt;/code&gt;, and typed arrays out of the box, you stop writing workaround code. Your documents go in, and they come out &lt;em&gt;identical&lt;/em&gt;. No reviver functions, no manual reconstruction.&lt;/p&gt;




&lt;h2&gt;
  
  
  Beyond Raw Speed: What LacertaDB Actually Offers
&lt;/h2&gt;

&lt;p&gt;Performance is one axis. Here's what the full picture looks like against PouchDB:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;LacertaDB&lt;/th&gt;
&lt;th&gt;PouchDB&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bundle size (minified)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~110 KB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~200 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serialization&lt;/td&gt;
&lt;td&gt;TurboSerial (binary)&lt;/td&gt;
&lt;td&gt;JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage backend&lt;/td&gt;
&lt;td&gt;IndexedDB + OPFS + localStorage&lt;/td&gt;
&lt;td&gt;IndexedDB (+ adapters)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query syntax&lt;/td&gt;
&lt;td&gt;MongoDB-style, 20+ operators&lt;/td&gt;
&lt;td&gt;Mango (pouchdb-find plugin)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Index types&lt;/td&gt;
&lt;td&gt;B-Tree, Hash, Full-text, Geo&lt;/td&gt;
&lt;td&gt;B-Tree equivalent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encryption&lt;/td&gt;
&lt;td&gt;AES-GCM-256, PBKDF2 Master Key Wrap&lt;/td&gt;
&lt;td&gt;❌ (requires plugin)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggregation pipeline&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$match&lt;/code&gt;, &lt;code&gt;$group&lt;/code&gt;, &lt;code&gt;$sort&lt;/code&gt;, &lt;code&gt;$lookup&lt;/code&gt;…&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Geospatial queries&lt;/td&gt;
&lt;td&gt;QuadTree &lt;code&gt;$near&lt;/code&gt;, &lt;code&gt;$within&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full-text search&lt;/td&gt;
&lt;td&gt;Built-in with CJK support&lt;/td&gt;
&lt;td&gt;❌ (requires plugin)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caching strategies&lt;/td&gt;
&lt;td&gt;LRU / LFU / TTL per collection&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Binary attachments&lt;/td&gt;
&lt;td&gt;OPFS-backed&lt;/td&gt;
&lt;td&gt;Blob-based&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CouchDB sync&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js / server-side&lt;/td&gt;
&lt;td&gt;❌ (browser-only)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The honest tradeoff: PouchDB has CouchDB sync and server-side support. If you need those, PouchDB is the right tool. LacertaDB is browser-native by design — it trades server compatibility for raw performance, smaller bundles, and features you'd otherwise need three plugins to bolt onto PouchDB.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Reach for LacertaDB
&lt;/h2&gt;

&lt;p&gt;LacertaDB was built for a specific class of application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Offline-first PWAs&lt;/strong&gt; that need to cache and query significant amounts of data locally without UI jank&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web3 dApps&lt;/strong&gt; that store blockchain state, wallet keys (with real encryption), or NFT metadata client-side&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data-heavy SPAs&lt;/strong&gt; where the difference between 100ms and 2,700ms on a bulk write is the difference between feeling native and feeling broken&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apps storing rich JavaScript types&lt;/strong&gt; — if your data model uses &lt;code&gt;Map&lt;/code&gt;, &lt;code&gt;Set&lt;/code&gt;, &lt;code&gt;Date&lt;/code&gt;, typed arrays, or &lt;code&gt;BigInt&lt;/code&gt;, you'll stop fighting your serializer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need CouchDB replication or server-side rendering, PouchDB remains excellent. But if your database lives in the browser and performance is non-negotiable, LacertaDB is worth a look.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&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; @pixagram/lacerta-db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LacertaDB&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;@pixagram/lacerta-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;lacerta&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;LacertaDB&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="nx"&gt;lacerta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myapp&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;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Store a document with types JSON can't handle&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Alice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;joined&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;preferences&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme&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;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lang&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;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]]),&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;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;admin&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;beta-tester&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
  &lt;span class="na"&gt;avatar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;137&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;78&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;71&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="c1"&gt;// PNG header bytes&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Query it back — every type is preserved, no revivers needed&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;admins&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;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$contains&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&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 source is on &lt;a href="https://github.com/pixagram-blockchain/LacertaDB" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, the package is on &lt;a href="https://www.npmjs.com/package/@pixagram/lacerta-db" rel="noopener noreferrer"&gt;npm&lt;/a&gt;, and the benchmark playground is included in the repo.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;LacertaDB is MIT-licensed and built by &lt;a href="https://pixagram.io" rel="noopener noreferrer"&gt;Pixagram SA&lt;/a&gt; in Zug, Switzerland.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>database</category>
    </item>
    <item>
      <title>A deep dive into LacertaDB</title>
      <dc:creator>Matias Affolter</dc:creator>
      <pubDate>Wed, 17 Dec 2025 13:18:17 +0000</pubDate>
      <link>https://dev.to/matias_affolter/a-deep-dive-into-lacertadb-1apc</link>
      <guid>https://dev.to/matias_affolter/a-deep-dive-into-lacertadb-1apc</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;LacertaDB: Why We Built a MongoDB-Like Database That Runs Entirely in Your Browser &lt;a href="https://codepen.io/Matias-Affolter/pen/ogjrQdB" rel="noopener noreferrer"&gt;DEMO&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;How we created a full-featured document database with encryption, indexing, and aggregation pipelines — all running client-side with zero backend.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem Nobody Was Solving
&lt;/h2&gt;

&lt;p&gt;Picture this: You're building a Web3 wallet, an offline-first PWA, or a privacy-focused note-taking app. You need to store sensitive data locally. Your options?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Solution&lt;/th&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;localStorage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5MB limit, no queries, plaintext only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raw IndexedDB&lt;/td&gt;
&lt;td&gt;Verbose API, no encryption, manual indexing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLite WASM&lt;/td&gt;
&lt;td&gt;2MB+ bundle, SQL syntax, no native encryption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PouchDB&lt;/td&gt;
&lt;td&gt;Sync-focused, no built-in encryption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dexie.js&lt;/td&gt;
&lt;td&gt;Great queries, but no encryption or OPFS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We needed something that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Stores encrypted documents with zero backend&lt;/li&gt;
&lt;li&gt;✅ Supports MongoDB-style queries&lt;/li&gt;
&lt;li&gt;✅ Handles binary files (images, PDFs)&lt;/li&gt;
&lt;li&gt;✅ Has proper indexes for performance&lt;/li&gt;
&lt;li&gt;✅ Works entirely offline&lt;/li&gt;
&lt;li&gt;✅ Fits in a reasonable bundle size&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing existed. So we built &lt;strong&gt;LacertaDB&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Makes LacertaDB Different
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. 🔐 Military-Grade Encryption, Zero Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// That's it. Your entire database is now encrypted.&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="nx"&gt;lacerta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSecureDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vault&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;123456&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Every document is automatically encrypted with AES-GCM-256&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;secrets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&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-xxx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;privateNotes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This is encrypted at rest&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;Under the hood:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AES-GCM-256&lt;/strong&gt; encryption (same as banks use)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PBKDF2&lt;/strong&gt; key derivation with 100,000 iterations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HMAC&lt;/strong&gt; integrity verification&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Constant-time comparisons&lt;/strong&gt; (no timing attacks)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most browser databases bolt encryption on as an afterthought. In LacertaDB, it's a first-class citizen — and it actually works when you change your PIN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This re-encrypts EVERY document in the database&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;changePin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;oldPin&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;newStrongerPin!&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;We've seen "encrypted" databases that lose all your data when you change the password. We fixed that.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. 🔍 Real MongoDB-Style Queries
&lt;/h3&gt;

&lt;p&gt;Not "inspired by MongoDB." Actually compatible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Find active premium users over 18, sorted by signup date&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&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;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;$and&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&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;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$in&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;pro&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;enterprise&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$gte&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$regex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@company&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;.com$&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;projection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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="mi"&gt;1&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="mi"&gt;1&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;&lt;strong&gt;20+ operators supported:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Operators&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Comparison&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$eq&lt;/code&gt;, &lt;code&gt;$ne&lt;/code&gt;, &lt;code&gt;$gt&lt;/code&gt;, &lt;code&gt;$gte&lt;/code&gt;, &lt;code&gt;$lt&lt;/code&gt;, &lt;code&gt;$lte&lt;/code&gt;, &lt;code&gt;$in&lt;/code&gt;, &lt;code&gt;$nin&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logical&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$and&lt;/code&gt;, &lt;code&gt;$or&lt;/code&gt;, &lt;code&gt;$not&lt;/code&gt;, &lt;code&gt;$nor&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Element&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$exists&lt;/code&gt;, &lt;code&gt;$type&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Array&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$all&lt;/code&gt;, &lt;code&gt;$elemMatch&lt;/code&gt;, &lt;code&gt;$size&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$regex&lt;/code&gt;, &lt;code&gt;$text&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Plus a full &lt;strong&gt;aggregation pipeline&lt;/strong&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="c1"&gt;// Sales report grouped by category&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;report&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;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$gte&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;startOfMonth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$category&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$sum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$amount&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;avgOrder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$avg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$amount&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$sort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs &lt;strong&gt;entirely in the browser&lt;/strong&gt;. No server. No network request.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. ⚡ Four Index Types for Real Performance
&lt;/h3&gt;

&lt;p&gt;Queries on 10,000 documents without indexes? Slow.&lt;br&gt;
With indexes? Instant.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// B-Tree for range queries and sorting&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;createdAt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;btree&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Hash for lightning-fast exact matches&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;unique&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;// Full-text search&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Geospatial for location queries&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;location&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;geo&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 geo index supports real geospatial queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Find coffee shops within 2km&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nearby&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;places&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;$near&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;47.3769&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;8.5417&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;maxDistance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="c1"&gt;// kilometers&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;coffee&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;h3&gt;
  
  
  4. 📁 Native Binary File Storage with OPFS
&lt;/h3&gt;

&lt;p&gt;Most browser databases choke on files. LacertaDB uses the &lt;strong&gt;Origin Private File System&lt;/strong&gt; (OPFS) — a modern API that gives you a real filesystem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Store a document with file attachments&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;docId&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;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Project Proposal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;author&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&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;attachments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;proposalPDF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// File object&lt;/span&gt;
      &lt;span class="nx"&gt;coverImage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// Blob&lt;/span&gt;
      &lt;span class="nx"&gt;spreadsheet&lt;/span&gt;      &lt;span class="c1"&gt;// Another File&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Retrieve with attachments&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;doc&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;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;docId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;includeAttachments&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;// doc._attachments = [{ name: 'proposal.pdf', data: Uint8Array, ... }]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Files are stored separately from document data, so your IndexedDB doesn't bloat. And yes, they're encrypted too.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. 🏗️ Fractal Architecture
&lt;/h3&gt;

&lt;p&gt;LacertaDB follows a "fractal" design where each layer encapsulates complexity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LacertaDB
└── Database (encryption, settings, migrations)
    └── Collection (CRUD, queries, indexes, events)
        └── Document (serialization, compression, packing)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means you can use just what you need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Full database with collections&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="nx"&gt;lacerta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&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;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Or just fast key-value storage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quickStore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme&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;mode&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="na"&gt;accent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#00ff88&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;
  
  
  Real-World Use Case: Web3 Wallet
&lt;/h2&gt;

&lt;p&gt;Here's how we use LacertaDB for secure key storage in a crypto wallet:&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LacertaDB&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;@pixagram/lacertadb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WalletManager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userPin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lacerta&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;LacertaDB&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lacerta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSecureDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wallet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userPin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wallets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wallets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Index for fast lookups&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wallets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;address&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;unique&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;createWallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;privateKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateKeyPair&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Store encrypted private key in secure vault&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;storePrivateKey&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;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-pk`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;privateKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Store public metadata&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wallets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;permanent&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;// permanent = protected from cleanup&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;signTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;walletName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Retrieve decrypted private key&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;privateKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrivateKey&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;walletName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-pk`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;signWithKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;privateKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;exportBackup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;backupPassword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Export everything, encrypted with a separate password&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lacerta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createBackup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;backupPassword&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The private keys are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Encrypted with AES-GCM-256&lt;/li&gt;
&lt;li&gt;Key derived from user PIN via PBKDF2&lt;/li&gt;
&lt;li&gt;Never exposed in plaintext&lt;/li&gt;
&lt;li&gt;Separate from document storage&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is &lt;strong&gt;production code&lt;/strong&gt; we ship.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance That Scales
&lt;/h2&gt;

&lt;p&gt;We benchmarked LacertaDB against common operations:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;1,000 docs&lt;/th&gt;
&lt;th&gt;10,000 docs&lt;/th&gt;
&lt;th&gt;100,000 docs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Insert (batch)&lt;/td&gt;
&lt;td&gt;45ms&lt;/td&gt;
&lt;td&gt;380ms&lt;/td&gt;
&lt;td&gt;3.2s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query (indexed)&lt;/td&gt;
&lt;td&gt;2ms&lt;/td&gt;
&lt;td&gt;3ms&lt;/td&gt;
&lt;td&gt;8ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query (full scan)&lt;/td&gt;
&lt;td&gt;12ms&lt;/td&gt;
&lt;td&gt;95ms&lt;/td&gt;
&lt;td&gt;890ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggregation&lt;/td&gt;
&lt;td&gt;8ms&lt;/td&gt;
&lt;td&gt;65ms&lt;/td&gt;
&lt;td&gt;580ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With proper indexes, queries stay fast even at scale. The built-in &lt;strong&gt;performance monitor&lt;/strong&gt; helps you optimize:&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;lacerta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;performanceMonitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startMonitoring&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// ... do operations ...&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lacerta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;performanceMonitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStats&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="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// {&lt;/span&gt;
&lt;span class="c1"&gt;//   opsPerSec: 245,&lt;/span&gt;
&lt;span class="c1"&gt;//   avgLatency: '4.12',&lt;/span&gt;
&lt;span class="c1"&gt;//   cacheHitRate: '89.3',&lt;/span&gt;
&lt;span class="c1"&gt;//   memoryUsageMB: '12.45'&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tips&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lacerta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;performanceMonitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOptimizationTips&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// ['Consider adding an index on frequently queried fields']&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Caching Layer Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Every query doesn't need to hit IndexedDB. LacertaDB includes three caching strategies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// LRU (Least Recently Used) - default&lt;/span&gt;
&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configureCacheStrategy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lru&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;maxSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;enabled&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;// LFU (Least Frequently Used) - for hot data&lt;/span&gt;
&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configureCacheStrategy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lfu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;maxSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// TTL (Time To Live) - for expiring data&lt;/span&gt;
&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configureCacheStrategy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ttl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt; &lt;span class="c1"&gt;// 1 minute&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cache hit rates of 80%+ are common, meaning 4 out of 5 queries return instantly from memory.&lt;/p&gt;




&lt;h2&gt;
  
  
  Schema Migrations Done Right
&lt;/h2&gt;

&lt;p&gt;Your data schema will evolve. LacertaDB has you covered:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;migration&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;MigrationManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;migration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addMigration&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Add user roles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;up&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isAdmin&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&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&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isAdmin&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;read&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;write&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;delete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;read&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;down&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isAdmin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Migrate forward&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;migration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runMigrations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Oops, rollback&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;migration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rollback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Migrations run per-document across all collections, with full rollback support.&lt;/p&gt;




&lt;h2&gt;
  
  
  Event-Driven When You Need It
&lt;/h2&gt;

&lt;p&gt;Hook into document lifecycle events:&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;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;afterAdd&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;syncToCloud&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;document_created&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;beforeDelete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;docId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;doc&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;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;docId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createBackup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Available events:&lt;/span&gt;
&lt;span class="c1"&gt;// beforeAdd, afterAdd&lt;/span&gt;
&lt;span class="c1"&gt;// beforeUpdate, afterUpdate  &lt;/span&gt;
&lt;span class="c1"&gt;// beforeDelete, afterDelete&lt;/span&gt;
&lt;span class="c1"&gt;// beforeGet, afterGet&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why "Lacerta"?
&lt;/h2&gt;

&lt;p&gt;Lacerta is the Latin word for lizard — specifically the genus that includes the common wall lizard. Why a lizard?&lt;/p&gt;

&lt;p&gt;🦎 &lt;strong&gt;Adaptable&lt;/strong&gt;: Lives anywhere, eats anything&lt;br&gt;
🦎 &lt;strong&gt;Fast&lt;/strong&gt;: Moves in bursts of incredible speed&lt;br&gt;
🦎 &lt;strong&gt;Resilient&lt;/strong&gt;: Survives harsh conditions&lt;br&gt;
🦎 &lt;strong&gt;Efficient&lt;/strong&gt;: Cold-blooded = minimal energy waste&lt;/p&gt;

&lt;p&gt;That's what we wanted for browser storage: adaptable to any use case, fast when it matters, resilient to failure, efficient with resources.&lt;/p&gt;

&lt;p&gt;Plus, we're based in Zug, Switzerland 🇨🇭 — lizards love sunny Alpine rocks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&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; @pixagram/lacertadb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LacertaDB&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;@pixagram/lacertadb&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;lacerta&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;LacertaDB&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Unencrypted database&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="nx"&gt;lacerta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myapp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Or encrypted&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secureDb&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;lacerta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSecureDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vault&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;myPin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Create collection and go&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;todos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;todos&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;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Try LacertaDB&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pending&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;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="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;pending&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;We're actively developing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔄 &lt;strong&gt;Sync adapters&lt;/strong&gt; for optional cloud backup&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;Query planner&lt;/strong&gt; for automatic index selection&lt;/li&gt;
&lt;li&gt;🧪 &lt;strong&gt;Schema validation&lt;/strong&gt; with JSON Schema&lt;/li&gt;
&lt;li&gt;🔌 &lt;strong&gt;Plugin system&lt;/strong&gt; for custom index types&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try It Today
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📦 &lt;strong&gt;npm&lt;/strong&gt;: &lt;code&gt;@pixagram/lacertadb&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;📚 &lt;strong&gt;Docs&lt;/strong&gt;: &lt;a href="https://github.com/pixagram/lacertadb" rel="noopener noreferrer"&gt;github.com/pixagram/lacertadb&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐛 &lt;strong&gt;Issues&lt;/strong&gt;: We actually respond!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building anything that needs local-first, encrypted, queryable storage in the browser — give LacertaDB a try. We think you'll love it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with 🦎 by &lt;a href="https://pixagram.io" rel="noopener noreferrer"&gt;Pixagram&lt;/a&gt; in Zug, Switzerland&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What browser storage challenges have you faced? Drop a comment below!&lt;/strong&gt; 👇&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>security</category>
      <category>database</category>
    </item>
    <item>
      <title>How We Built an AI That Transforms Your Photos Into Pixel Art in 10 Seconds (And Why It Matters for Web3)</title>
      <dc:creator>Matias Affolter</dc:creator>
      <pubDate>Tue, 28 Oct 2025 10:31:55 +0000</pubDate>
      <link>https://dev.to/matias_affolter/how-we-built-an-ai-that-transforms-your-photos-into-pixel-art-in-10-seconds-and-why-it-matters-for-47pk</link>
      <guid>https://dev.to/matias_affolter/how-we-built-an-ai-that-transforms-your-photos-into-pixel-art-in-10-seconds-and-why-it-matters-for-47pk</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; We created an AI-powered pixel art generator that preserves facial features while creating authentic retro art. But here's the kicker – pixel art is 100x lighter than regular images, making it perfect for storing forever on the blockchain. Let me show you how we did it. &lt;a href="https://huggingface.co/spaces/primerz/pixagram" rel="noopener noreferrer"&gt;TRY IT&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem We're Solving
&lt;/h2&gt;

&lt;p&gt;Picture this: You love NFTs, but uploading a 2MB photo to the blockchain costs you an arm and a leg. Most NFT platforms don't actually store your art on-chain – they just store a link that could break tomorrow. Meanwhile, creating genuine pixel art by hand takes hours, even for professionals.&lt;/p&gt;

&lt;p&gt;We thought: &lt;em&gt;What if we could convert any photo into authentic pixel art that's light enough to live permanently on-chain?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's how &lt;a href="https://pixagram.io" rel="noopener noreferrer"&gt;Pixagram&lt;/a&gt; was born.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  The Magic Behind the Curtain
&lt;/h2&gt;

&lt;p&gt;Our AI pipeline is like a sophisticated assembly line, where each station does one specific job really well. Here's what happens when you upload a photo:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://pixagram.io/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fpixa-pics%2Fpixa-pics.github.io%2Fmaster%2Fsrc%2Fimages%2Fog%2Fpixagram_logo_og_s.jpeg" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://pixagram.io/" rel="noopener noreferrer" class="c-link"&gt;
            Pixagram.io - Social Media Blockchain
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Use the social media blockchain — Pixagram — where your artworks become as enduring as diamond. Transform photos into pixel art using pixa.pics, then mint, post, and thrive on our NFT-powered network.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpixagram.io%2Fsrc%2Fimages%2Ffavicon.ico"&gt;
          pixagram.io
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;




&lt;h3&gt;
  
  
  1. &lt;strong&gt;Face Detection &amp;amp; Enhancement&lt;/strong&gt; (The Foundation)
&lt;/h3&gt;

&lt;p&gt;First, we use InsightFace to detect faces in your image. But we don't just find the face – we actually &lt;em&gt;understand&lt;/em&gt; it. The system extracts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;512-dimensional facial embeddings (basically a mathematical fingerprint of your face)&lt;/li&gt;
&lt;li&gt;Facial keypoints (eyes, nose, mouth positions)&lt;/li&gt;
&lt;li&gt;Age and gender estimates&lt;/li&gt;
&lt;li&gt;Facial structure data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's where it gets interesting: we crop the face with 30% padding around it (for context), then enhance it through multiple stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resize to optimal dimensions&lt;/li&gt;
&lt;li&gt;Sharpen features (1.5x boost)&lt;/li&gt;
&lt;li&gt;Enhance contrast (1.1x)&lt;/li&gt;
&lt;li&gt;Adjust brightness (1.05x)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of this as giving the AI the best possible view of your face before the transformation begins.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Dual Embedding System&lt;/strong&gt; (The Secret Sauce)
&lt;/h3&gt;

&lt;p&gt;Most AI art generators use one type of embedding. We use &lt;strong&gt;two&lt;/strong&gt; working together:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CLIP Embeddings:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;These understand the &lt;em&gt;semantic meaning&lt;/em&gt; of your face&lt;/li&gt;
&lt;li&gt;They know "this is a smiling woman with blue eyes"&lt;/li&gt;
&lt;li&gt;Perfect for maintaining identity at a conceptual level&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;InsightFace Embeddings:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;These understand the &lt;em&gt;geometric structure&lt;/em&gt; of your face&lt;/li&gt;
&lt;li&gt;They know exact distances between features&lt;/li&gt;
&lt;li&gt;Perfect for preserving facial accuracy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By combining both, we get the best of semantic understanding AND structural precision.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;The Transformation Pipeline&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now the real magic happens. We use a modified Stable Diffusion XL pipeline with several specialized components:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;a) Custom Pixel Art LORA&lt;/strong&gt;&lt;br&gt;
We trained a specialized LORA (Low-Rank Adaptation) on authentic pixel art. This teaches the model what genuine pixel art should look like – not just blocky images, but art with proper dithering, limited color palettes, and that nostalgic retro feel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;b) InstantID ControlNet&lt;/strong&gt;&lt;br&gt;
This maintains facial structure using the keypoints we extracted earlier. It's like drawing a skeleton that the AI must follow – ensuring your eyes stay where your eyes should be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;c) Zoe Depth ControlNet&lt;/strong&gt;&lt;br&gt;
This preserves the 3D structure of your image. It understands depth – what's in front, what's behind – keeping your photo's spatial relationships intact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;d) IP-Adapter Integration&lt;/strong&gt;&lt;br&gt;
This injects our dual embeddings directly into the diffusion process through cross-attention. It's constantly whispering to the AI: "remember, this is what the person's face looks like."&lt;/p&gt;
&lt;h3&gt;
  
  
  4. &lt;strong&gt;Post-Processing Polish&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;After generation, we apply color matching in LAB color space. This ensures skin tones stay consistent with the original photo – no random orange or purple faces.&lt;/p&gt;

&lt;p&gt;For faces, we create soft masks with feathered edges, allowing seamless blending between the pixelated transformation and facial features.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Technical Stack
&lt;/h2&gt;

&lt;p&gt;Here's what powers the system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Base Model: Stable Diffusion XL (custom checkpoint: "horizon")
- Face Analysis: InsightFace (AnteLope v2)
- Scheduler: LCM (enables 12-step generation)
- ControlNets: InstantID + Zoe Depth
- Image Encoding: CLIP Vision Model
- Pipeline: Img2Img (preserves structure)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Parameters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Steps: 12 (thanks to LCM scheduler)&lt;/li&gt;
&lt;li&gt;CFG Scale: 1.0-1.5 (LCM sweet spot)&lt;/li&gt;
&lt;li&gt;Img2Img Strength: 0.55 (balances transformation vs. fidelity)&lt;/li&gt;
&lt;li&gt;Identity Preservation: 1.3x (boosted for maximum face accuracy)&lt;/li&gt;
&lt;li&gt;Resolution: Auto-optimized to 896×1152 or 832×1216&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Matters: The Blockchain Advantage
&lt;/h2&gt;

&lt;p&gt;Here's where things get really interesting. Let's talk numbers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A typical photo:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Average size: 2-3 MB&lt;/li&gt;
&lt;li&gt;On IPFS: Still requires external hosting&lt;/li&gt;
&lt;li&gt;Link can break if hosting fails&lt;/li&gt;
&lt;li&gt;Expensive to store on-chain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pixagram pixel art:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Average size: 15-20 KB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;That's 100-150x smaller&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Can be embedded directly on-chain&lt;/li&gt;
&lt;li&gt;Stored forever in the blockchain itself&lt;/li&gt;
&lt;li&gt;No external dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Our Blockchain: Built for This
&lt;/h3&gt;

&lt;p&gt;Pixagram runs on a fork of HIVE/STEEM, optimized for social media and NFTs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;72 KB maximum post capacity&lt;/strong&gt; – perfect for pixel art&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3-second block time&lt;/strong&gt; – near-instant confirmation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proof-of-Brain consensus&lt;/strong&gt; – rewards creators&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;24 elected witnesses&lt;/strong&gt; – truly decentralized&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you create pixel art on Pixagram, it's not just stored on-chain – it becomes &lt;em&gt;part&lt;/em&gt; of the chain. Your art will exist as long as the blockchain exists. No broken links. No lost hosting. Forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Actually Performs
&lt;/h2&gt;

&lt;p&gt;Let's be real about what this AI can and can't do:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it excels at:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Portraits with clear facial features&lt;/li&gt;
&lt;li&gt;Photos with good lighting&lt;/li&gt;
&lt;li&gt;Maintaining facial identity (80-95% similarity)&lt;/li&gt;
&lt;li&gt;Creating authentic pixel art aesthetic&lt;/li&gt;
&lt;li&gt;Fast generation (10 seconds average)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What's challenging:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Very low-light photos&lt;/li&gt;
&lt;li&gt;Extremely complex scenes&lt;/li&gt;
&lt;li&gt;Photos where faces are tiny or obscured&lt;/li&gt;
&lt;li&gt;Multiple faces (focuses on largest)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We've tested thousands of photos, and the system achieves 80-90% average face similarity, with best cases hitting 95%+.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;Want to see it in action? We've made it dead simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://pixagram.io" rel="noopener noreferrer"&gt;Pixagram.io&lt;/a&gt;&lt;/strong&gt; – Our full platform with blockchain integration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://huggingface.co/spaces/primerz/pixagram" rel="noopener noreferrer"&gt;Hugging Face Space&lt;/a&gt;&lt;/strong&gt; – Try the AI without signup&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Hugging Face space lets you experiment with all the parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adjust identity preservation strength&lt;/li&gt;
&lt;li&gt;Control pixel art intensity&lt;/li&gt;
&lt;li&gt;Fine-tune depth and structure preservation&lt;/li&gt;
&lt;li&gt;Enable/disable color matching&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;We're not just building an AI art generator. We're building a &lt;strong&gt;Web3 social network&lt;/strong&gt; where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every post rewards creators&lt;/li&gt;
&lt;li&gt;Art lives forever on-chain&lt;/li&gt;
&lt;li&gt;No corporation owns your content&lt;/li&gt;
&lt;li&gt;Lightweight pixel art enables true decentralization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The NFT market has 11.6 million users worldwide, but most platforms are either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exciting but centralized&lt;/strong&gt; (like Instagram)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decentralized but boring&lt;/strong&gt; (like traditional NFT marketplaces)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pixagram aims to be both: &lt;strong&gt;exciting AND decentralized&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Deep Dive: The Code
&lt;/h2&gt;

&lt;p&gt;For the developers reading this, here's how the core generation works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Core pipeline initialization
&lt;/span&gt;&lt;span class="n"&gt;pipe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StableDiffusionXLControlNetImg2ImgPipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_single_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;controlnet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;instantid_controlnet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth_controlnet&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;torch_dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float16&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Face analysis
&lt;/span&gt;&lt;span class="n"&gt;faces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;face_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="n"&gt;image_array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;face_embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;face&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;normed_embedding&lt;/span&gt;  &lt;span class="c1"&gt;# 512-dim
&lt;/span&gt;
&lt;span class="c1"&gt;# IP-Adapter projection
&lt;/span&gt;&lt;span class="n"&gt;image_embeds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;image_proj_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;insightface_embeds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Generation with dual ControlNets
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;input_image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;control_image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;face_keypoints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth_map&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;controlnet_conditioning_scale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;added_cond_kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image_embeds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;image_embeds&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;strength&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;num_inference_steps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;guidance_scale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.45&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key insight? &lt;strong&gt;Multi-modal conditioning&lt;/strong&gt;. We're hitting the model from multiple angles simultaneously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text prompts guide style&lt;/li&gt;
&lt;li&gt;Depth maps preserve structure
&lt;/li&gt;
&lt;li&gt;Keypoints maintain facial geometry&lt;/li&gt;
&lt;li&gt;Embeddings preserve identity&lt;/li&gt;
&lt;li&gt;Img2Img keeps overall composition&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;We're currently in testnet alpha, with mainnet beta coming in October 2025. Our roadmap includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enhanced multi-face support&lt;/li&gt;
&lt;li&gt;More art styles beyond pixel art&lt;/li&gt;
&lt;li&gt;Advanced blockchain governance&lt;/li&gt;
&lt;li&gt;Creator monetization tools&lt;/li&gt;
&lt;li&gt;NFT marketplace integration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Join the Revolution
&lt;/h2&gt;

&lt;p&gt;Whether you're a:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Developer&lt;/strong&gt; – Fork our code, build on our blockchain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Artist&lt;/strong&gt; – Create pixel art that lives forever&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crypto enthusiast&lt;/strong&gt; – Early adopter advantages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Curious human&lt;/strong&gt; – Just want to pixelate your selfie&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'd love to have you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it now:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Production platform: &lt;a href="https://pixagram.io" rel="noopener noreferrer"&gt;pixagram.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Experiment freely: &lt;a href="https://huggingface.co/spaces/primerz/pixagram" rel="noopener noreferrer"&gt;Hugging Face Space&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Get in touch: &lt;a href="mailto:omnibus@pixagram.io"&gt;omnibus@pixagram.io&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The internet promised to make information free and accessible forever. But in practice, links break, servers die, and companies disappear. Blockchain offers a second chance at that promise – but only if we can make it practical.&lt;/p&gt;

&lt;p&gt;Pixel art isn't just nostalgia. It's a 100x compression that makes permanent, decentralized storage actually feasible. It's small enough to embed in a blockchain, distinctive enough to be valuable as art, and our AI makes it accessible to everyone.&lt;/p&gt;

&lt;p&gt;That's the magic formula: &lt;strong&gt;AI accessibility + artistic value + blockchain permanence&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Create artworks lasting forever on the blockchain while getting rewarded."&lt;/em&gt; That's not just our tagline – it's the future we're building.&lt;/p&gt;

&lt;p&gt;Come help us build it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What do you think? Would you want your photos turned into permanent blockchain pixel art? Let me know in the comments!&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; #ai #blockchain #web3 #nft #pixelart #machinelearning #stablediffusion #opensource&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About Pixagram:&lt;/strong&gt;&lt;br&gt;
Pixagram SA is a Swiss company (Zug) developing Web3.0 social media infrastructure. Our technology combines AI art generation with lightweight blockchain storage, creating the first truly on-chain social network for pixel art NFTs.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>blockchain</category>
      <category>javascript</category>
    </item>
    <item>
      <title>TurboSerial! High-Performance JS Serialization</title>
      <dc:creator>Matias Affolter</dc:creator>
      <pubDate>Tue, 02 Sep 2025 10:20:19 +0000</pubDate>
      <link>https://dev.to/matias_affolter/turboserial-high-performance-js-serialization-m70</link>
      <guid>https://dev.to/matias_affolter/turboserial-high-performance-js-serialization-m70</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A deep dive into &lt;a href="https://www.npmjs.com/package/@pixagram/turboserial" rel="noopener noreferrer"&gt;TurboSerial on NPM&lt;/a&gt;, a new JavaScript serialization library that outperforms MessagePack and CBOR through SIMD-inspired design, and why it might be your next choice for performance-critical applications.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the world of web development, especially in performance-critical applications, the way we handle data is paramount. Sending large JSON payloads can be a significant bottleneck. This is where binary serialization formats like &lt;strong&gt;&lt;em&gt;MessagePack&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;CBOR&lt;/em&gt;&lt;/strong&gt; have traditionally stepped in, offering more compact and faster alternatives.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;u&gt;But what if we could push the boundaries of performance even further? &lt;br&gt;
&lt;/u&gt;&lt;br&gt;
Enter &lt;strong&gt;&lt;em&gt;TurboSerial&lt;/em&gt;&lt;/strong&gt;, a new JavaScript serialization library designed from the ground up for extreme speed, inspired by modern, low-level CPU optimizations.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;We ran a comprehensive benchmark suite of &lt;strong&gt;over 110 tests&lt;/strong&gt; comparing TurboSerial against the well-established MessagePack (v5) and CBOR (cbor-js). The results were eye-opening.&lt;/p&gt;

&lt;h2&gt;
  
  
  See For Yourself
&lt;/h2&gt;

&lt;p&gt;We believe in transparency and verifiable results. You can run the exact same benchmark suite in your own browser and see the performance difference firsthand.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codepen.io/Matias-Affolter/pen/bNVQLmN" rel="noopener noreferrer"&gt;Run the interactive benchmark&lt;/a&gt;&lt;/p&gt;

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

&lt;h2&gt;
  
  
  The Verdict: Compatibility and Speed
&lt;/h2&gt;

&lt;p&gt;When it comes to handling the full spectrum of JavaScript types, the difference is stark. While the established libraries struggle with modern features like &lt;strong&gt;BigInt, complex Maps, and circular references&lt;/strong&gt;, TurboSerial handles them with ease.&lt;/p&gt;

&lt;p&gt;Compatibility is only half the story. In performance benchmarks, TurboSerial consistently outperformed the others, especially in scenarios involving large, structured data.&lt;/p&gt;

&lt;p&gt;For small and medium-sized JSON-like objects, TurboSerial was consistently the fastest. But its true power was revealed when handling large TypedArrays, where it was dramatically faster than the competition. This isn't magic; it's by design.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes TurboSerial So Fast?
&lt;/h2&gt;

&lt;p&gt;TurboSerial's performance advantage comes from architectural decisions inspired by cutting-edge, low-level libraries like simdjson. It leverages concepts that allow modern CPUs to process data in parallel.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. SIMD (Single Instruction, Multiple Data)
&lt;/h3&gt;

&lt;p&gt;At its core, SIMD is a CPU feature that allows a single instruction to be executed on multiple data points simultaneously.  Think of it as a multi-lane highway versus a single-lane road. Instead of processing one number at a time in an array, TurboSerial's SIMD-optimized routines can process blocks of 4, 8, or even more numbers in a single CPU cycle. This provides a massive throughput advantage for numeric arrays and TypedArrays.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Byte Alignment
&lt;/h3&gt;

&lt;p&gt;Modern CPUs are fastest when they can read data from memory addresses that are multiples of 4 or 8. Accessing misaligned data can cause the CPU to perform extra work, slowing down the entire process. TurboSerial is meticulous about aligning data in its internal buffers, ensuring that every read and write operation is as fast as the hardware allows.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Inspired by SIMD.js
&lt;/h3&gt;

&lt;p&gt;While the original SIMD.js specification was put on hold, its polyfills and the underlying "branchless" design philosophy were a major inspiration. By designing algorithms that avoid conditional branching (if/else) and instead process data in predictable blocks, TurboSerial minimizes CPU pipeline stalls, keeping the processor fed with data and operating at maximum efficiency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with the API
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;TurboSerial maintains a simple and intuitive API, making it easy to drop into any project.&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;TurboSerial&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;@pixagram/turboserial&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;serializer&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;TurboSerial&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Serialize any JavaScript value&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello, World!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;123456789012345678901234567890&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;array&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&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="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]]),&lt;/span&gt;
  &lt;span class="na"&gt;typedArray&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Float32Array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serialized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;deserialized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deserialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serialized&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For power users, the library offers a rich set of configuration options to fine-tune its behavior for specific use cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advanced Configuration&lt;/strong&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serializer&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;TurboSerial&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;compression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// (Upcoming feature)&lt;/span&gt;
  &lt;span class="na"&gt;deduplication&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;// Store duplicate objects/strings once&lt;/span&gt;
  &lt;span class="na"&gt;shareArrayBuffers&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;// Share ArrayBuffer references&lt;/span&gt;
  &lt;span class="na"&gt;simdOptimization&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;// Enable SIMD-like optimizations for arrays&lt;/span&gt;
  &lt;span class="na"&gt;detectCircular&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;// Handle circular references&lt;/span&gt;
  &lt;span class="na"&gt;memoryPoolSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;65536&lt;/span&gt;         &lt;span class="c1"&gt;// Initial memory pool size in bytes&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;While MessagePack and CBOR are excellent, established libraries, TurboSerial represents a step forward, leveraging modern hardware capabilities to offer superior performance and wider type compatibility. For applications where every millisecond and every data type counts, TurboSerial is a powerful new contender that is well worth considering for your serialization needs.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>algorithms</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The Dark Reality of NFTs: An Obscure and Limited Landscape</title>
      <dc:creator>Matias Affolter</dc:creator>
      <pubDate>Fri, 04 Oct 2024 14:50:04 +0000</pubDate>
      <link>https://dev.to/matias_affolter/the-dark-reality-of-nfts-an-obscure-and-limited-landscape-2ioc</link>
      <guid>https://dev.to/matias_affolter/the-dark-reality-of-nfts-an-obscure-and-limited-landscape-2ioc</guid>
      <description>&lt;p&gt;Non-Fungible Tokens (NFTs) have emerged as one of the most talked-about innovations in digital ownership, creativity, and technology. Yet, despite their widespread visibility, the world of NFTs remains shrouded in complexity, making them difficult to access for the majority of people. This article sheds light on the current constraints within the NFT ecosystem, highlighting the gaps between awareness and action, the gender disparities, and the overwhelming complexity of minting and acquiring NFTs. In the end, we’ll see why NFTs have yet to reach the mainstream and remain confined to niche circles.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Crypto and NFTs are a path to financial independence, so it’s important that women and girls know about them. When I first heard about blockchain, I didn’t think it was for me. But I realized that it can be an inclusive space for women and people of color. I think crypto has the power to radically advance women’s rights.” – &lt;em&gt;Maliha Abidi, Artist &amp;amp; Author&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. &lt;strong&gt;Widespread Awareness but Minimal Engagement&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;A Global Phenomenon Known by Many, Used by Few&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Surveys like those from &lt;strong&gt;Statista (2022)&lt;/strong&gt; show that NFTs are not lacking in awareness. Between &lt;strong&gt;40-50%&lt;/strong&gt; of internet users globally are aware of NFTs, representing hundreds of millions of people. Countries like the U.S., parts of Europe, and Asia (particularly Japan and South Korea) show even higher rates of awareness, often exceeding &lt;strong&gt;50%&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;However, the critical issue lies in translating this awareness into action. Despite millions knowing about NFTs, only a small fraction actively engages with them. Fewer than &lt;strong&gt;1%&lt;/strong&gt; of those aware of NFTs have actually created or purchased one. This chasm between awareness and engagement points to deeper underlying problems, primarily related to technical barriers and the perceived complexity of the process.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Key Insight:&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The widespread knowledge of NFTs doesn't translate into widespread action. Despite high awareness, a minuscule percentage of people are willing or able to engage due to perceived and real barriers.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. &lt;strong&gt;Creating NFTs: Technically Possible, Yet Unlikely&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Technical and Financial Barriers of Becoming a Creator&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;While platforms like &lt;strong&gt;OpenSea&lt;/strong&gt; have streamlined the process of creating NFTs, the reality remains that fewer than &lt;strong&gt;3-5 million people&lt;/strong&gt; have ever minted an NFT, a figure that is tiny compared to the vast pool of individuals aware of the concept. Why such a low adoption rate? A few key reasons stand out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Technical Complexity&lt;/strong&gt;: Creating an NFT is not as simple as posting an image on Instagram. It involves understanding cryptocurrency wallets, gas fees, blockchain protocols, and other technical aspects, which are intimidating for newcomers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Financial Barriers&lt;/strong&gt;: Minting NFTs often incurs costs (gas fees on Ethereum, for example), which further limits participation to those willing to bear these expenses, unlike free-to-use platforms like social media.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Moreover, many NFT creation platforms lack the user-friendliness of everyday apps like &lt;strong&gt;LinkedIn&lt;/strong&gt; or &lt;strong&gt;Instagram&lt;/strong&gt;, further alienating the average user. The current NFT minting experience requires a familiarity with blockchain infrastructure that many people either do not possess or are unwilling to invest time to learn.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Key Insight:&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Although it is technically possible to create NFTs, the process is overly complex and costly for the average internet user, making the barrier to entry significantly higher than on mainstream platforms.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. &lt;strong&gt;Acquiring NFTs: A Gendered and Imbalanced Landscape&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Gender Divide in NFT Ownership&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;While about &lt;strong&gt;15-20 million&lt;/strong&gt; people globally have purchased or own NFTs, the ownership landscape is notably skewed in terms of gender. Only about &lt;strong&gt;1 in 4 NFT holders&lt;/strong&gt; are women, which points to a significant gender imbalance in the space. This statistic is troubling when considering the potential for NFTs to democratize digital ownership and creativity across demographics.&lt;/p&gt;

&lt;p&gt;This imbalance isn't just about who is buying NFTs; it's also about who feels welcomed and capable within the NFT ecosystem. Many women report feeling alienated or underserved by current platforms, which cater more to male tech enthusiasts and crypto investors.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Buyers Outnumber Creators, But Complexity Remains&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;There are roughly &lt;strong&gt;four times more buyers than creators&lt;/strong&gt; in the NFT space, but even becoming a buyer comes with its own challenges. For someone unfamiliar with cryptocurrencies, acquiring an NFT can feel unnecessarily complicated, with steps that require setting up a digital wallet, purchasing crypto, navigating decentralized platforms, and managing security risks. This isn’t as simple as making a purchase on Amazon or posting on a social media platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Key Insight:&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Gender disparity in NFT ownership and the complex process of purchasing an NFT further limit the democratization of this technology, alienating both potential creators and buyers alike.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. &lt;strong&gt;Platform Constraints and Narrow Use Cases&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;NFT Platforms Are Not Mainstream-Friendly&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Despite the potential of NFTs, the platforms that support them remain niche. &lt;strong&gt;OpenSea&lt;/strong&gt;, &lt;strong&gt;Rarible&lt;/strong&gt;, and &lt;strong&gt;Foundation&lt;/strong&gt; dominate the space, but they are complex, highly specialized, and cater to a specific subset of tech-savvy individuals. While social media platforms like &lt;strong&gt;Instagram&lt;/strong&gt; and &lt;strong&gt;LinkedIn&lt;/strong&gt; could potentially open the doors for wider NFT adoption, they do not currently offer easy integration for NFTs.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Limited Functionality Restricts Use Cases&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Even for those who do engage, the use cases for NFTs remain limited. Most NFTs are centered around digital art and collectibles, with very few use cases in mainstream or practical scenarios that would appeal to the average person. As a result, the audience is confined to collectors, artists, and crypto enthusiasts, leaving broader applications largely untapped.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Key Insight:&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;NFT platforms, as they exist today, cater to a very specific, niche audience. Their functionality and user experience are nowhere near the simplicity of mainstream platforms like Instagram or LinkedIn, limiting the potential for broader adoption.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: &lt;strong&gt;The Unseen Obstacles Hindering NFTs’ Mainstream Adoption&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The NFT space, though promising, is still mired in obscurity, complexity, and imbalance. While millions are aware of NFTs, very few are able or willing to engage with them. The barriers to entry, whether through creation or acquisition, are too high for most, and the platforms that dominate the space are inaccessible to the average internet user. Furthermore, gender disparities persist, highlighting the fact that the NFT ecosystem is far from inclusive.&lt;/p&gt;

&lt;p&gt;To unlock the full potential of NFTs, platforms must simplify the user experience, eliminate financial and technical barriers, and widen the use cases beyond digital art and collectibles. Without these changes, NFTs will remain a niche technology, overshadowed by the overwhelming complexity that defines it today.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;Statista, &lt;strong&gt;Global NFT Awareness Survey (2022)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;NonFungible.com, &lt;strong&gt;NFT Ecosystem Report (2022)&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cryptocurrency</category>
      <category>blockchain</category>
      <category>web3</category>
    </item>
    <item>
      <title>🚀 Introducing LacertaDB: A Simple and Powerful JavaScript Database</title>
      <dc:creator>Matias Affolter</dc:creator>
      <pubDate>Thu, 03 Oct 2024 08:06:09 +0000</pubDate>
      <link>https://dev.to/matias_affolter/introducing-lacertadb-a-simple-and-powerful-javascript-database-2j7c</link>
      <guid>https://dev.to/matias_affolter/introducing-lacertadb-a-simple-and-powerful-javascript-database-2j7c</guid>
      <description>&lt;p&gt;I’m excited to introduce &lt;strong&gt;LacertaDB&lt;/strong&gt;, a new database I’ve designed that’s &lt;strong&gt;simple&lt;/strong&gt;, &lt;strong&gt;flexible&lt;/strong&gt;, and &lt;strong&gt;powerful&lt;/strong&gt;. It's still evolving, but even now, it brings some unique capabilities to the table that I think you'll find fascinating. &lt;/p&gt;

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

&lt;p&gt;Let me walk you through what makes &lt;strong&gt;LacertaDB&lt;/strong&gt; so interesting:&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Key Features:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Effortless Compression &amp;amp; Encryption&lt;/strong&gt;:
Simply by setting the properties &lt;code&gt;_compressed&lt;/code&gt; or &lt;code&gt;_encrypted&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;, the &lt;strong&gt;Document&lt;/strong&gt; class automatically recognizes the need to compress or encrypt your data. No extra effort required on your part! It handles both packing and unpacking seamlessly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supports Various Data Types&lt;/strong&gt;:
Thanks to &lt;strong&gt;joyson&lt;/strong&gt;, you can pack any type of JavaScript data into &lt;strong&gt;LacertaDB&lt;/strong&gt;—from &lt;code&gt;TypedArray&lt;/code&gt;, to &lt;code&gt;Errors&lt;/code&gt;, and everything JSON supports as well.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Efficient Metadata Storage with LocalStorage&lt;/strong&gt;:
What makes &lt;strong&gt;LacertaDB&lt;/strong&gt; architecturally unique is that it uses &lt;strong&gt;localStorage&lt;/strong&gt; to store metadata for each database, even when multiple collections exist. Though &lt;strong&gt;localStorage&lt;/strong&gt; is capped at 5MB, that’s &lt;strong&gt;plenty for metadata&lt;/strong&gt;.
Here’s what we keep track of:

&lt;ul&gt;
&lt;li&gt;📏 &lt;strong&gt;Length&lt;/strong&gt; of documents&lt;/li&gt;
&lt;li&gt;🆔 &lt;strong&gt;IDs&lt;/strong&gt; of documents&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;Sizes&lt;/strong&gt; of documents&lt;/li&gt;
&lt;li&gt;🛡️ &lt;strong&gt;Permanence&lt;/strong&gt; (&lt;code&gt;_permanent&lt;/code&gt; property ensures documents don't get deleted during auto-compaction)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This approach allows &lt;strong&gt;LacertaDB&lt;/strong&gt; to quickly retrieve the &lt;strong&gt;database state&lt;/strong&gt; and &lt;strong&gt;automatically compact&lt;/strong&gt; the database without querying &lt;strong&gt;IndexedDB&lt;/strong&gt;—which, while fast, isn’t as speedy as &lt;strong&gt;localStorage&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔧 The Inner Workings
&lt;/h2&gt;

&lt;p&gt;While &lt;strong&gt;LacertaDB&lt;/strong&gt; needs a bit more work, it already performs &lt;strong&gt;remarkably well&lt;/strong&gt;. You can easily pack and unpack hundreds of documents, whether compressed, encrypted, or not, within &lt;strong&gt;milliseconds&lt;/strong&gt;! 🚀&lt;/p&gt;

&lt;p&gt;And all of this while managing metadata in a lightweight manner and avoiding unnecessary overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  💡 Why Use LacertaDB?
&lt;/h2&gt;

&lt;p&gt;If you're looking for a &lt;strong&gt;simple-to-use database&lt;/strong&gt; that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Supports &lt;strong&gt;any type of JavaScript object&lt;/strong&gt; 🛠️&lt;/li&gt;
&lt;li&gt;Handles data compression/encryption with a single property change 🔒&lt;/li&gt;
&lt;li&gt;Allows you to easily manage collections using instance methods 📁&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then &lt;strong&gt;LacertaDB&lt;/strong&gt; might just be what you need! &lt;/p&gt;




&lt;h2&gt;
  
  
  ✨ How to Use It
&lt;/h2&gt;

&lt;p&gt;You can get started by installing it from 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;lacertadb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example usage:&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;LacertaDB&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;lacertadb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Create a database instance and get a (new) collection&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LacertaDB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myDatabase&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;coll&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;getCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;documents&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Add a document with encryption and compression&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;coll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addDocument&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;document1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;_compressed&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;_encrypted&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;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Some sensitive data&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;password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Display the query result (you'll see it's encrypted)&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;coll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// Display the document as original&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;coll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;document1&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;password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;🔍 &lt;strong&gt;Want to know more?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Check out the &lt;strong&gt;&lt;a href="https://www.npmjs.com/package/lacertadb" rel="noopener noreferrer"&gt;NPM package&lt;br&gt;
&lt;/a&gt;&lt;/strong&gt; or start using &lt;strong&gt;LacertaDB&lt;/strong&gt; in your projects today. Feedback and contributions are always welcome!&lt;/p&gt;

&lt;p&gt;If you’ve ever thought, “I just need a &lt;strong&gt;simple&lt;/strong&gt; database to store my JavaScript objects,” look no further. &lt;strong&gt;LacertaDB&lt;/strong&gt; is here to make your life easier. 🎉&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“Code is like humor. When you have to explain it, it’s bad.”&lt;/strong&gt; – Cory House&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Give &lt;strong&gt;LacertaDB&lt;/strong&gt; a try, and see how it fits into your next project! 🙌&lt;/p&gt;




&lt;p&gt;This database is evolving, but with its ability to &lt;strong&gt;compress&lt;/strong&gt;, &lt;strong&gt;encrypt&lt;/strong&gt;, and &lt;strong&gt;handle various data types&lt;/strong&gt; in &lt;strong&gt;milliseconds&lt;/strong&gt;, I believe it’s a solid choice for projects that need a &lt;strong&gt;lightweight and flexible&lt;/strong&gt; storage solution. 🚀&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>database</category>
      <category>npm</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
