<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>LoraDB Blog</title>
        <link>https://loradb.com/blog</link>
        <description>Engineering notes, architecture pieces, release notes, and design writing from the LoraDB team.</description>
        <lastBuildDate>Mon, 18 May 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>Copyright © 2026 LoraDB.</copyright>
        <item>
            <title><![CDATA[LoraDB v0.11: Query playground in your browser]]></title>
            <link>https://loradb.com/blog/loradb-v0-11-playground</link>
            <guid>https://loradb.com/blog/loradb-v0-11-playground</guid>
            <pubDate>Mon, 18 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[LoraDB v0.11 launches play.loradb.com, an in-browser playground for writing LoraDB queries, viewing graph/table/JSON results, inspecting schema hints, saving snapshots, and sharing query URLs. No server, no install.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="LoraDB v0.11 — query playground in your browser." src="https://loradb.com/assets/images/loradb-v0-11-playground-header-07cacb8652ba3772a304c5254dcfdbd4.png" width="1280" height="400" class="img__Ss2"></p>
<p>LoraDB v0.11 is a surface release.</p>
<p>v0.5 made the engine stream. v0.6 made persistence feel like a system.
v0.7 was a process release. v0.8 made plans and runtime metrics easier
to inspect from bindings. v0.9 gave the planner a schema catalog. v0.10
made the function library a library.</p>
<p>v0.11 puts the engine behind a URL. <a href="https://play.loradb.com/" target="_blank" rel="noopener noreferrer" class=""><code>play.loradb.com</code></a>
is a browser playground for writing LoraDB queries, running them against
an in-tab database, and seeing the results as a graph, table, or JSON.
It ships as a static Next.js export and runs the database through
WebAssembly in the browser.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-the-playground-is">What the playground is<a href="https://loradb.com/blog/loradb-v0-11-playground#what-the-playground-is" class="hash-link" aria-label="Direct link to What the playground is" title="Direct link to What the playground is" translate="no">​</a></h2>
<p>Open a tab. Write a LoraDB query. See the graph.</p>
<p>The playground loads three of LoraDB's workspace packages:</p>
<ul>
<li class=""><a href="https://www.npmjs.com/package/@loradb/lora-wasm" target="_blank" rel="noopener noreferrer" class=""><code>@loradb/lora-wasm</code></a> runs
the Rust engine through WebAssembly.</li>
<li class=""><a href="https://www.npmjs.com/package/@loradb/lora-query" target="_blank" rel="noopener noreferrer" class=""><code>@loradb/lora-query</code></a>
provides the Monaco-based query editor, formatting, highlighting,
completion, and diagnostics.</li>
<li class=""><a href="https://www.npmjs.com/package/@loradb/lora-graph-canvas" target="_blank" rel="noopener noreferrer" class=""><code>@loradb/lora-graph-canvas</code></a>
renders graph-shaped results as nodes and relationships.</li>
</ul>
<p>Around those packages the app adds query tabs, result views, a schema
browser, history, saved queries, snapshots, a command palette, keyboard
shortcuts, and copyable query links. Saved work lives in IndexedDB and
<code>localStorage</code>; the hosted app does not receive your graph.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="same-engine-not-a-demo-dialect">Same engine, not a demo dialect<a href="https://loradb.com/blog/loradb-v0-11-playground#same-engine-not-a-demo-dialect" class="hash-link" aria-label="Direct link to Same engine, not a demo dialect" title="Direct link to Same engine, not a demo dialect" translate="no">​</a></h2>
<p>The playground is not a hand-written browser mock of LoraDB. It uses
the same Rust crates that power the bindings, compiled for WASM and
loaded from the browser bundle:</p>
<ul>
<li class="">the same parser and analyzer;</li>
<li class="">the same planner and executor;</li>
<li class="">the same value model for nodes, relationships, paths, temporal
values, points, vectors, lists, and maps;</li>
<li class="">the same snapshot byte format exposed by the WASM binding.</li>
</ul>
<p>That matters for docs and bug reports. A query you can reduce in the
playground is a useful reproduction for the engine, not just a screenshot
of a separate teaching tool.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-workbench">The workbench<a href="https://loradb.com/blog/loradb-v0-11-playground#the-workbench" class="hash-link" aria-label="Direct link to The workbench" title="Direct link to The workbench" translate="no">​</a></h2>
<p>The first release keeps the layout intentionally simple: editor on top,
results below, and an activity sidebar for supporting tools. The split
between editor and results is resizable and persisted locally.</p>
<p><strong>Editor.</strong> Multi-tab query editing with LoraDB-aware highlighting,
completion, formatting on command, diagnostics, and the familiar
<code>Cmd/Ctrl+Enter</code> run shortcut.</p>
<p><strong>Results.</strong> The same result can be inspected several ways:</p>
<ul>
<li class=""><em>Graph</em> renders nodes and relationships when the result contains graph
entities.</li>
<li class=""><em>Table</em> gives a compact grid for scalar and structured columns.</li>
<li class=""><em>JSON</em> shows the adapted row payload.</li>
<li class=""><em>Plan</em> shows parser/analyzer information such as diagnostics,
variables, labels, relationship types, and parameters. Full
<code>explain</code>/<code>profile</code> execution plans remain binding and HTTP APIs.</li>
</ul>
<p><strong>Sidebar.</strong> Saved queries, schema, snapshots, history, and settings
live behind the activity bar. The schema panel introspects labels,
relationship types, property keys, and per-label counts from the current
database.</p>
<p><strong>Inspector.</strong> Selecting graph entities opens their labels, type,
properties, and internal IDs in the inspector drawer.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="sharing-a-query">Sharing a query<a href="https://loradb.com/blog/loradb-v0-11-playground#sharing-a-query" class="hash-link" aria-label="Direct link to Sharing a query" title="Direct link to Sharing a query" translate="no">​</a></h2>
<p>The Share action copies a URL with the query body encoded in the hash as
<code>#q=&lt;compressed-query&gt;</code>. The codec uses <code>lz-string</code>'s URL-safe encoding;
it is not base64 and it does not encode your local database.</p>
<p>That boundary is deliberate. A shared link is a way to send the query
text, not a way to publish your graph. If the recipient needs the same
data, export a snapshot and send the <code>.lorasnap</code> file alongside the
query link.</p>
<p>Snapshots use the same byte format as the WASM binding:</p>
<ol>
<li class="">Run the seed statements.</li>
<li class="">Open Snapshots and create a snapshot.</li>
<li class="">Export the snapshot file.</li>
<li class="">Share the snapshot and the query URL together.</li>
</ol>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-runs-where">What runs where<a href="https://loradb.com/blog/loradb-v0-11-playground#what-runs-where" class="hash-link" aria-label="Direct link to What runs where" title="Direct link to What runs where" translate="no">​</a></h2>
<p>The playground is a fully static export. There are no API routes, no
server actions, and no shared hosted database. <code>next build</code> writes
HTML, JavaScript, CSS, and WASM assets into <code>apps/play.loradb.com/out</code>;
Cloudflare Pages serves those files.</p>
<p>That has three practical consequences:</p>
<ul>
<li class=""><strong>No backend account.</strong> There is no sign-in and no hosted graph for
the app to sync with.</li>
<li class=""><strong>Local persistence.</strong> Saved queries, history, settings, snapshots,
and the auto-restored graph are browser-origin data.</li>
<li class=""><strong>Browser-sized workloads.</strong> The engine is real, but this surface is
still one browser tab. It is for learning, debugging, examples, and
small local graphs, not load testing.</li>
</ul>
<p>A graph that needs server-side durability, multiple clients, operational
controls, or production ingress still belongs in an application binding
or the HTTP server.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-the-playground-does-not-do-yet">What the playground does not do yet<a href="https://loradb.com/blog/loradb-v0-11-playground#what-the-playground-does-not-do-yet" class="hash-link" aria-label="Direct link to What the playground does not do yet" title="Direct link to What the playground does not do yet" translate="no">​</a></h2>
<p>The release is useful because the boundaries are clear:</p>
<ul>
<li class=""><strong>No parameter drawer.</strong> The editor can detect parameter names, but
this first UI does not expose a host-side params panel. Docs examples
meant to run directly in the playground should use trusted inline
literals or seed data.</li>
<li class=""><strong>No multi-database selector.</strong> The browser origin owns one local
playground database.</li>
<li class=""><strong>No true query abort.</strong> The Cancel button drops the pending result
from the UI; the current WASM <code>execute</code> call still runs until it
returns.</li>
<li class=""><strong>No remote import.</strong> Import accepts snapshot files from the local
machine. The app does not fetch remote URLs to seed a graph.</li>
<li class=""><strong>Hash links only.</strong> Share state lives in the URL hash so a static
export can refresh cleanly.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="new-package-surfaces">New package surfaces<a href="https://loradb.com/blog/loradb-v0-11-playground#new-package-surfaces" class="hash-link" aria-label="Direct link to New package surfaces" title="Direct link to New package surfaces" translate="no">​</a></h2>
<p>Two UI packages are versioned with this release:</p>
<ul>
<li class=""><a href="https://www.npmjs.com/package/@loradb/lora-query" target="_blank" rel="noopener noreferrer" class=""><code>@loradb/lora-query</code></a>
packages the query editor pieces used by the playground.</li>
<li class=""><a href="https://www.npmjs.com/package/@loradb/lora-graph-canvas" target="_blank" rel="noopener noreferrer" class=""><code>@loradb/lora-graph-canvas</code></a>
packages the graph canvas used by the result view.</li>
</ul>
<p>They are published under the same Business Source License 1.1 release
terms as the rest of the repository. The docs site itself remains
separately MIT-licensed.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="how-v011-fits-the-journey">How v0.11 fits the journey<a href="https://loradb.com/blog/loradb-v0-11-playground#how-v011-fits-the-journey" class="hash-link" aria-label="Direct link to How v0.11 fits the journey" title="Direct link to How v0.11 fits the journey" translate="no">​</a></h2>
<p>The earlier releases made the engine more capable and observable.
v0.11 makes it easier to touch.</p>
<p>That changes what a docs page, PR comment, or support thread can ask of
a reader. Instead of starting with "clone the repo and wire up a
binding", we can often start with "open the playground and run this".</p>
<p>The next obvious improvements are a parameters drawer, seeded docs links,
and smoother snapshot handoff from docs into the playground.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="read-next">Read next<a href="https://loradb.com/blog/loradb-v0-11-playground#read-next" class="hash-link" aria-label="Direct link to Read next" title="Direct link to Read next" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://play.loradb.com/" target="_blank" rel="noopener noreferrer" class="">Open the playground</a></li>
<li class=""><a class="" href="https://loradb.com/playground">Playground page on loradb.com</a></li>
<li class=""><a class="" href="https://loradb.com/docs/getting-started/playground">Playground guide</a></li>
<li class=""><a class="" href="https://loradb.com/docs/cookbook">Cookbook — queries that run as-is</a></li>
<li class=""><a class="" href="https://loradb.com/docs/getting-started/wasm">WASM binding guide</a></li>
<li class=""><a class="" href="https://loradb.com/docs/limitations">Limitations</a></li>
</ul>
<p>v0.11 is the release where you can try LoraDB before installing it.</p>]]></content:encoded>
            <category>Release notes</category>
            <category>Announcement</category>
            <category>Playground</category>
            <category>WASM</category>
            <category>Tooling</category>
        </item>
        <item>
            <title><![CDATA[LoraDB v0.10: one canonical name per concept]]></title>
            <link>https://loradb.com/blog/loradb-v0-10-namespaced-functions</link>
            <guid>https://loradb.com/blog/loradb-v0-10-namespaced-functions</guid>
            <pubDate>Wed, 13 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[LoraDB v0.10 introduces a namespaced built-in function library — 236 signatures grouped under list.*, string.*, map.*, math.*, temporal.*, vector.*, node.*, edge.*, path.*, cast.*, type.*, and friends — with 38 Cypher and migration aliases so existing queries keep working.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="LoraDB v0.10 — one canonical name per concept." src="https://loradb.com/assets/images/loradb-v0-10-namespaced-functions-header-b7b62c3a89a0c12eafea3504f76de9cd.png" width="1280" height="400" class="img__Ss2"></p>
<p>LoraDB v0.10 is a function-surface release.</p>
<p>v0.5 made the engine stream. v0.6 made persistence feel like a system.
v0.7 was a process release. v0.8 made the planner and executor
observable. v0.9 gave the planner a real schema catalog.</p>
<p>v0.10 does the same thing for the function library: the engine now has
one canonical name per concept, grouped into namespaces, with the
analyzer, executor, optimizer, docs, and binding tests all reading from
the same table.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-shape-of-the-change">The shape of the change<a href="https://loradb.com/blog/loradb-v0-10-namespaced-functions#the-shape-of-the-change" class="hash-link" aria-label="Direct link to The shape of the change" title="Direct link to The shape of the change" translate="no">​</a></h2>
<p>LoraDB used to ship a flat soup of function names: <code>head</code>, <code>tolower</code>,
<code>substring</code>, <code>coalesce</code>, <code>vector.similarity.cosine</code>, <code>randomUUID</code>,
<code>toIntegerOrNull</code>, <code>keys</code>, <code>id</code>, <code>type</code>. Some were single-word, some
were dotted, some came from Cypher, some from convenience.</p>
<p>That worked while there were sixty of them. With v0.10 there are 236,
covering text, numbers, lists, maps, temporal values, bytes, bits,
geo, vectors, graph entities, paths, type inspection, and explicit
casts.</p>
<p>The fix is structural, not cosmetic. Every built-in now lives at
<code>&lt;namespace&gt;.&lt;operation&gt;</code>, both segments <code>snake_case</code>:</p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">RETURN string.upper('hello')                      AS greeting,
       list.first([1, 2, 3])                      AS head,
       math.clamp($x, 0, 100)                     AS bounded,
       temporal.between(date('2024-01-01'), date()) AS age,
       value.coalesce($preferred, $fallback, 'n/a') AS pick,
       cast.try($maybe_int, INTEGER)              AS as_int;</code></pre></div>
<p>The rules are enforced module-wide, not per function:</p>
<ul>
<li class="">Two segments only — <code>namespace.operation</code>.</li>
<li class=""><code>snake_case</code> for both segments.</li>
<li class="">Namespaces name the value family or concern (<code>list.*</code>, <code>string.*</code>,
<code>vector.*</code>, <code>node.*</code>). Runtime type questions live under <code>type.*</code>;
conversions live under <code>cast.*</code>.</li>
<li class="">One operation per concept. Behaviour varies by arguments, not by
suffix — no <code>sortMaps</code> / <code>sortNodes</code> / <code>sortText</code>, just <code>list.sort</code>.</li>
<li class="">Predicates return <code>BOOL</code> and read as a question (<code>is_</code>, <code>has_</code>,
<code>contains</code>, <code>equal_unordered</code>, <code>all_distinct</code>).</li>
<li class="">Pure functions only. Mutating procedures live in the procedure
dispatcher, not in the function library.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="sixteen-namespaces">Sixteen namespaces<a href="https://loradb.com/blog/loradb-v0-10-namespaced-functions#sixteen-namespaces" class="hash-link" aria-label="Direct link to Sixteen namespaces" title="Direct link to Sixteen namespaces" translate="no">​</a></h2>
<p>The full catalog groups into sixteen pure namespaces and four
storage-aware namespaces.</p>
<table><thead><tr><th>Namespace</th><th>What lives there</th></tr></thead><tbody><tr><td><code>list.*</code></td><td>Set-like ops, indexing, reshaping, windowing, sampling.</td></tr><tr><td><code>string.*</code></td><td>Case, search, slicing, padding, encoding, regex.</td></tr><tr><td><code>text.*</code></td><td>Fuzzy distance, similarity, phonetic match.</td></tr><tr><td><code>map.*</code></td><td>Map lookup, patching, nested paths, entries, group/index by.</td></tr><tr><td><code>number.*</code></td><td>Formatting, radix conversion, numeric predicates.</td></tr><tr><td><code>bits.*</code></td><td>Integer bit operations.</td></tr><tr><td><code>math.*</code></td><td>Numeric formulas, rounding, trig, constants, random.</td></tr><tr><td><code>temporal.*</code></td><td>Now/today/timestamp, parse/format, get/truncate, between.</td></tr><tr><td><code>bytes.*</code></td><td>Length, encode/decode, compress/decompress.</td></tr><tr><td><code>crypto.*</code></td><td><code>blake3</code>, <code>crc32</code>.</td></tr><tr><td><code>uuid.*</code></td><td><code>new</code>, <code>from_string</code>, <code>is_valid</code>.</td></tr><tr><td><code>json.*</code></td><td>Encode, decode, path lookup.</td></tr><tr><td><code>geo.*</code></td><td>Point distance and bbox predicates.</td></tr><tr><td><code>vector.*</code></td><td>Dimension and similarity helpers.</td></tr><tr><td><code>type.*</code></td><td><code>type.of(x)</code>, <code>type.is(x, TYPE)</code> — runtime type questions.</td></tr><tr><td><code>cast.*</code></td><td><code>cast.to</code>, <code>cast.try</code>, <code>cast.can</code> — explicit conversion.</td></tr><tr><td><code>node.*</code></td><td><code>node.id</code>, <code>node.labels</code>, <code>node.has_label</code>, <code>node.keys</code>, <code>node.properties</code>.</td></tr><tr><td><code>edge.*</code></td><td><code>edge.id</code>, <code>edge.type</code>, <code>edge.start</code>, <code>edge.end</code>, <code>edge.keys</code>, <code>edge.properties</code>.</td></tr><tr><td><code>path.*</code></td><td><code>path.nodes</code>, <code>path.edges</code>, <code>path.length</code>, <code>path.first</code>, <code>path.last</code>.</td></tr><tr><td><code>value.*</code></td><td>Polymorphic helpers that apply across families: <code>value.size</code>, <code>value.coalesce</code>, <code>value.is_null</code>, <code>value.id</code>.</td></tr></tbody></table>
<p>That gives every developer a search shape: when you don't remember the
exact name, you remember the namespace and tab through it.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="a-single-source-of-truth">A single source of truth<a href="https://loradb.com/blog/loradb-v0-10-namespaced-functions#a-single-source-of-truth" class="hash-link" aria-label="Direct link to A single source of truth" title="Direct link to A single source of truth" translate="no">​</a></h2>
<p>The catalog lives in one file, owned by the analyzer:</p>
<div class="language-rust codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-rust codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token comment">// crates/lora-analyzer/src/analyzer/builtin_signatures.rs</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">pub</span><span class="token plain"> </span><span class="token keyword">const</span><span class="token plain"> </span><span class="token constant">BUILTIN_SPECS</span><span class="token punctuation">:</span><span class="token plain"> </span><span class="token operator">&amp;</span><span class="token punctuation">[</span><span class="token class-name">BuiltinSpec</span><span class="token punctuation">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token operator">&amp;</span><span class="token punctuation">[</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token function">spec</span><span class="token punctuation">(</span><span class="token string">"list.first"</span><span class="token punctuation">,</span><span class="token plain">            </span><span class="token number">1</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token class-name">Some</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token function">spec</span><span class="token punctuation">(</span><span class="token string">"list.sort"</span><span class="token punctuation">,</span><span class="token plain">             </span><span class="token number">1</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token class-name">Some</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token function">spec</span><span class="token punctuation">(</span><span class="token string">"string.upper"</span><span class="token punctuation">,</span><span class="token plain">          </span><span class="token number">1</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token class-name">Some</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token function">spec</span><span class="token punctuation">(</span><span class="token string">"string.slice"</span><span class="token punctuation">,</span><span class="token plain">          </span><span class="token number">2</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token class-name">Some</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token function">spec</span><span class="token punctuation">(</span><span class="token string">"math.clamp"</span><span class="token punctuation">,</span><span class="token plain">            </span><span class="token number">3</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token class-name">Some</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token function">spec</span><span class="token punctuation">(</span><span class="token string">"temporal.between"</span><span class="token punctuation">,</span><span class="token plain">      </span><span class="token number">2</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token class-name">Some</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token function">spec</span><span class="token punctuation">(</span><span class="token string">"vector.similarity"</span><span class="token punctuation">,</span><span class="token plain">     </span><span class="token number">2</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token class-name">Some</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token function">spec_type</span><span class="token punctuation">(</span><span class="token string">"cast.try"</span><span class="token punctuation">,</span><span class="token plain">         </span><span class="token number">2</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token class-name">Some</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token operator">&amp;</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token comment">// …236 entries total</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token punctuation">]</span><span class="token punctuation">;</span><br></div></code></pre></div></div>
<p>Every entry carries an arity range and, where it matters, the argument
slots that accept type literals (<code>DATE</code>, <code>INTEGER</code>, <code>VECTOR&lt;FLOAT32&gt;(384)</code>)
or enum-like literals (<code>COSINE</code>, <code>EUCLIDEAN</code>).</p>
<p>The executor never re-declares names. It exposes a <code>dispatch</code> arm per
namespace, and a small drift-safety test in the executor walks
<code>BUILTIN_SPECS</code> and asserts that every entry resolves to an
implementation:</p>
<div class="language-rust codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-rust codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token attribute attr-name">#[test]</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">fn</span><span class="token plain"> </span><span class="token function-definition function">every_signature_has_a_dispatch_arm</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> </span><span class="token comment">/* … */</span><span class="token plain"> </span><span class="token punctuation">}</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token attribute attr-name">#[test]</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">fn</span><span class="token plain"> </span><span class="token function-definition function">every_signature_is_two_segments_and_snake_case</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> </span><span class="token comment">/* … */</span><span class="token plain"> </span><span class="token punctuation">}</span><br></div></code></pre></div></div>
<p>If you add a row to the table without an executor arm, the test fails.
If you add a non-snake-case name, the test fails. If you add a dispatch
arm without a signature, the analyzer rejects calls before they reach
you. Drift between the surface and the runtime is no longer possible.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="aliases--for-the-names-you-already-type">Aliases — for the names you already type<a href="https://loradb.com/blog/loradb-v0-10-namespaced-functions#aliases--for-the-names-you-already-type" class="hash-link" aria-label="Direct link to Aliases — for the names you already type" title="Direct link to Aliases — for the names you already type" translate="no">​</a></h2>
<p>The new tree would be a worse experience if every existing query had to
be rewritten. v0.10 keeps every familiar Cypher spelling working through
analyzer aliases:</p>
<table><thead><tr><th>You write</th><th>Resolves to</th></tr></thead><tbody><tr><td><code>head(xs)</code>, <code>last(xs)</code></td><td><code>list.first(xs)</code>, <code>list.last(xs)</code></td></tr><tr><td><code>coalesce(a, b, …)</code></td><td><code>value.coalesce(a, b, …)</code></td></tr><tr><td><code>toLower(s)</code>, <code>toUpper(s)</code></td><td><code>string.lower(s)</code>, <code>string.upper(s)</code></td></tr><tr><td><code>left(s, n)</code>, <code>right(s, n)</code>, <code>substring(s, …)</code></td><td><code>string.prefix</code>, <code>string.suffix</code>, <code>string.slice</code></td></tr><tr><td><code>reverse(x)</code>, <code>size(x)</code>, <code>keys(x)</code>, <code>properties(x)</code></td><td><code>value.reverse</code>, <code>value.size</code>, <code>value.keys</code>, <code>value.properties</code></td></tr><tr><td><code>length(p)</code></td><td><code>path.length(p)</code></td></tr><tr><td><code>id(x)</code>, <code>labels(n)</code>, <code>type(r)</code></td><td><code>value.id(x)</code>, <code>node.labels(n)</code>, <code>edge.type(r)</code></td></tr><tr><td><code>now()</code>, <code>timestamp()</code>, <code>timezone()</code></td><td><code>temporal.now</code>, <code>temporal.timestamp</code>, <code>temporal.timezone</code></td></tr><tr><td><code>random()</code>, <code>randomUUID()</code></td><td><code>math.random</code>, <code>uuid.new</code></td></tr><tr><td><code>toInteger(x)</code> / <code>toString(x)</code> / <code>toFloat(x)</code> / <code>toBoolean(x)</code></td><td><code>cast.to(x, INTEGER | STRING | FLOAT | BOOLEAN)</code></td></tr><tr><td><code>toIntegerOrNull</code> / <code>toStringOrNull</code> / <code>toFloatOrNull</code> / <code>toBooleanOrNull</code></td><td><code>cast.try(x, TYPE)</code></td></tr></tbody></table>
<p>A second alias family covers in-house migrations from earlier LoraDB
spellings:</p>
<table><thead><tr><th>Migration alias</th><th>Canonical</th></tr></thead><tbody><tr><td><code>list.find_index</code>, <code>list.find_indexes</code></td><td><code>list.index_of</code>, <code>list.indexes_of</code></td></tr><tr><td><code>vector.dim</code></td><td><code>vector.dimension</code></td></tr><tr><td><code>value.first_non_null</code></td><td><code>value.coalesce</code></td></tr><tr><td><code>type.cast</code>, <code>type.try_cast</code>, <code>type.can_cast</code></td><td><code>cast.to</code>, <code>cast.try</code>, <code>cast.can</code></td></tr></tbody></table>
<p>Aliases resolve during analyzer lowering. They never reach the
executor. That means no per-binding plumbing, no aliasing-induced
ambiguity in plans, and one place to look when you wonder why a name
exists.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="why-this-is-the-right-shape-now">Why this is the right shape now<a href="https://loradb.com/blog/loradb-v0-10-namespaced-functions#why-this-is-the-right-shape-now" class="hash-link" aria-label="Direct link to Why this is the right shape now" title="Direct link to Why this is the right shape now" translate="no">​</a></h2>
<p>Three forces pushed v0.10 to land before any larger function surface
work:</p>
<p><strong>Predictability.</strong> With sixteen namespaces a developer can guess a
name without grepping. The right names for "give me the first list
element" or "lowercase this string" or "tell me whether this map has a
key" are reachable from the namespace alone.</p>
<p><strong>Drift safety.</strong> The analyzer signature catalog and executor dispatch
used to be two parallel sources of truth. They drifted. Tests caught
some of that; not all of it. The drift-safety tests in v0.10 make the
two sources structurally identical — adding a function means adding one
line of signature plus one match arm, and the test suite enforces the
pairing.</p>
<p><strong>Cross-binding consistency.</strong> The bindings (<code>@loradb/lora-node</code>,
<code>@loradb/lora-wasm</code>, <code>lora-python</code>, <code>lora-ruby</code>, <code>lora-go</code>, the
<code>lora-ffi</code> C ABI) call Cypher through the same parser. When the parser
exposes one canonical name per concept, every binding inherits the same
surface for free. The binding tests in v0.10 exercise the namespaced
names directly so a regression in one shows up in all five test
suites.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="pipeline-updates">Pipeline updates<a href="https://loradb.com/blog/loradb-v0-10-namespaced-functions#pipeline-updates" class="hash-link" aria-label="Direct link to Pipeline updates" title="Direct link to Pipeline updates" translate="no">​</a></h2>
<p>Behind the surface, every layer learned about namespaces:</p>
<ul>
<li class=""><strong>Parser</strong> — <code>function_invocation</code> now accepts <code>namespace.operation</code>
invocations and parses the full canonical form. The grammar uses a
small <code>namespace = (symbolic_name ~ dot)+</code> rule reused for procedure
calls.</li>
<li class=""><strong>Analyzer</strong> — <code>BUILTIN_SPECS</code> and <code>BUILTIN_ALIASES</code> resolve every
function reference. Aliases are normalized to the canonical
spelling before type checking, so the resolved tree always reads
canonically.</li>
<li class=""><strong>Compiler</strong> — <code>plan_namespaced_call</code> covers the new arms in one
place; the optimizer can recognize storage-aware namespaces
(<code>node.*</code>, <code>edge.*</code>, <code>path.*</code>, <code>value.*</code>) and lower them where
beneficial.</li>
<li class=""><strong>Executor</strong> — the function library was reorganised into one module
per namespace, with a small dispatcher that returns <code>None</code> for any
name outside the namespace tree.</li>
<li class=""><strong>Database / store / server</strong> — internal call sites now use the
canonical names. The on-disk format and snapshot codec are
unchanged; only the function names in code paths moved.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="examples">Examples<a href="https://loradb.com/blog/loradb-v0-10-namespaced-functions#examples" class="hash-link" aria-label="Direct link to Examples" title="Direct link to Examples" translate="no">​</a></h2>
<p>Some queries the new shape makes pleasant to write.</p>
<p><strong>A search ranking that combines vector similarity and structure</strong></p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">MATCH (d:Doc)
WITH d, vector.similarity(d.embedding, $query) AS score
WHERE score &gt; 0.6
MATCH (d)-[:MENTIONS]-&gt;(e:Entity)
RETURN d.id,
       string.upper(d.title)               AS title,
       value.coalesce(d.summary, '(none)') AS summary,
       score,
       list.unique(collect(e.name))        AS entities
ORDER BY score DESC
LIMIT 5;</code></pre></div>
<p><strong>Working a list down to a single shape</strong></p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">RETURN list.first(value.coalesce($items, []))                AS head,
       list.last($items)                                     AS tail,
       list.unique(list.flatten($items))                     AS uniq,
       list.zip($items, list.range(1, list.size($items)))    AS indexed;</code></pre></div>
<p><strong>Parsing and normalising user input</strong></p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">WITH cast.try($input, INTEGER) AS as_int,
     string.trim(cast.to($input, STRING)) AS as_string
RETURN value.coalesce(as_int, 0)                AS count,
       string.lower(as_string)                  AS key,
       type.of($input)                          AS reported_type,
       type.is($input, INTEGER)                 AS was_int;</code></pre></div>
<p><strong>Temporal arithmetic without remembering the constant names</strong></p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">WITH temporal.now() AS now, $birthday AS dob
RETURN temporal.between(dob, now)               AS lived,
       temporal.in_days(dob, now)               AS days_lived,
       temporal.truncate(now, 'day')            AS today,
       temporal.add(now, duration('P1Y'))       AS next_year;</code></pre></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="binding-behaviour">Binding behaviour<a href="https://loradb.com/blog/loradb-v0-10-namespaced-functions#binding-behaviour" class="hash-link" aria-label="Direct link to Binding behaviour" title="Direct link to Binding behaviour" translate="no">​</a></h2>
<p>The bindings did not change shape — <code>db.execute(query, params)</code> returns
the same tagged values it did in v0.9, and the vector / temporal /
spatial helpers still produce the same wire format. What changed is
that every binding's parameter and similarity test now exercises the
namespaced builtin path through Cypher, including via type-cast
literals (<code>[1.0, 0.0, 0.0]::VECTOR&lt;FLOAT32&gt;(3)</code>).</p>
<p>If a binding test exercised <code>vector.similarity.cosine(...)</code> before, it
now exercises <code>vector.similarity(...)</code>. Behavioural parity is checked
in the same place as before.</p>
<p>Existing user code keeps working through the alias table.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="documentation">Documentation<a href="https://loradb.com/blog/loradb-v0-10-namespaced-functions#documentation" class="hash-link" aria-label="Direct link to Documentation" title="Direct link to Documentation" translate="no">​</a></h2>
<p>Every namespace has its own reference page; the function overview was
rewritten around the canonical names. A new developer-facing page,
<code>docs/developer/functions.md</code>, documents the rules for adding new
built-ins: signature, executor module, integration test in
<code>builtin_namespaces.rs</code>, docs entry, drift-safety check. The
contribution surface is one page now, not five.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="breaking-changes-and-migration">Breaking changes and migration<a href="https://loradb.com/blog/loradb-v0-10-namespaced-functions#breaking-changes-and-migration" class="hash-link" aria-label="Direct link to Breaking changes and migration" title="Direct link to Breaking changes and migration" translate="no">​</a></h2>
<p>There are no on-disk migrations. The WAL and snapshot codecs are
unchanged.</p>
<p>For Cypher callers, the canonical names are new, but the old spellings
keep working through aliases — including the cases the analyzer used to
special-case (<code>coalesce</code> is variadic, <code>vector.similarity</code> accepts
either two or three arguments, <code>cast.to</code> accepts a type literal in arg
position 1).</p>
<p>For Rust callers, the executor function library was reorganised into
modules per namespace. If you were calling individual builtin
implementations from outside the engine — generally not a supported
path — the module paths changed. If you were calling them through
<code>Compiler::compile</code> + <code>Executor::run</code>, nothing changed.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="notable-fixes">Notable fixes<a href="https://loradb.com/blog/loradb-v0-10-namespaced-functions#notable-fixes" class="hash-link" aria-label="Direct link to Notable fixes" title="Direct link to Notable fixes" translate="no">​</a></h2>
<ul>
<li class="">The analyzer now reports a single, consistent error for unknown
function names — including a "did you mean" hint when the name
matches a namespace prefix.</li>
<li class=""><code>value.coalesce(...)</code> short-circuits on the first non-null argument
even when later arguments would have errored.</li>
<li class="">Numeric predicates (<code>number.is_finite</code>, <code>number.is_nan</code>,
<code>number.is_integer</code>) accept both <code>INTEGER</code> and <code>FLOAT</code> and never
raise.</li>
<li class="">Type-literal arguments are validated at parse time, not at runtime,
so <code>cast.to(x, UNKNOWN_TYPE)</code> fails before the query executes.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="how-v010-fits-the-journey">How v0.10 fits the journey<a href="https://loradb.com/blog/loradb-v0-10-namespaced-functions#how-v010-fits-the-journey" class="hash-link" aria-label="Direct link to How v0.10 fits the journey" title="Direct link to How v0.10 fits the journey" translate="no">​</a></h2>
<p>v0.5 made the engine stream. v0.6 made persistence feel like a system.
v0.7 was a process release. v0.8 made the planner and executor
observable. v0.9 gave the planner a real schema catalog.</p>
<p>v0.10 makes the function surface itself a system. The analyzer owns
public signatures, the executor owns behavior, the docs read off the
same names, and the binding tests pin the cross-language contract. The
next time we add a function, we add it once.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="still-open">Still open<a href="https://loradb.com/blog/loradb-v0-10-namespaced-functions#still-open" class="hash-link" aria-label="Direct link to Still open" title="Direct link to Still open" translate="no">​</a></h2>
<p>This release does not add new computational power. Every function in
the canonical tree was reachable in v0.9 — under a different name.
What v0.10 buys is a place to put the next forty functions without
making the surface less learnable.</p>
<p>The natural extensions are user-defined functions (UDFs) registered
into the same catalog, a documented procedure surface to mirror what
<code>db.index.fulltext.queryNodes</code> / <code>db.index.vector.queryNodes</code> already
look like, and a <code>SHOW FUNCTIONS</code> introspection that reads the
analyzer signature table directly.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="read-next">Read next<a href="https://loradb.com/blog/loradb-v0-10-namespaced-functions#read-next" class="hash-link" aria-label="Direct link to Read next" title="Direct link to Read next" translate="no">​</a></h2>
<ul>
<li class=""><a class="" href="https://loradb.com/docs/functions/overview">Function overview</a></li>
<li class=""><a class="" href="https://loradb.com/docs/developer/functions">Building built-in functions</a></li>
<li class=""><a href="https://github.com/lora-db/lora/blob/main/docs/reference/cypher-support-matrix.md" target="_blank" rel="noopener noreferrer" class="">Cypher support matrix</a></li>
<li class=""><a class="" href="https://loradb.com/docs/limitations">Limitations</a></li>
</ul>
<p>v0.10 is the release where LoraDB's function library stops being a
list of names and starts being a library.</p>]]></content:encoded>
            <category>Release notes</category>
            <category>Announcement</category>
            <category>Cypher</category>
            <category>Design</category>
            <category>Architecture</category>
        </item>
        <item>
            <title><![CDATA[LoraDB v0.9: indexes, constraints, and a real schema catalog]]></title>
            <link>https://loradb.com/blog/loradb-v0-9-indexes-and-constraints</link>
            <guid>https://loradb.com/blog/loradb-v0-9-indexes-and-constraints</guid>
            <pubDate>Sun, 10 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[LoraDB v0.9 adds a first-class index and constraint catalog: RANGE/TEXT/POINT/LOOKUP/VECTOR/FULLTEXT indexes, node and relationship constraints, catalog-backed scans, and full-text and vector query procedures that survive WAL recovery and snapshot reload.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="LoraDB v0.9 — indexes, constraints, and a real schema catalog." src="https://loradb.com/assets/images/loradb-v0-9-indexes-and-constraints-header-0cdd1537b8825e8725a9e525ef7fd817.png" width="1280" height="400" class="img__Ss2"></p>
<p>LoraDB v0.9 is a schema-catalog release.</p>
<p>v0.5 made the engine stream. v0.6 made persistence feel like a system.
v0.7 was a process release. v0.8 made the planner and executor
observable. v0.9 closes the next gap: the planner now has a real schema
catalog to plan against, and the engine has real constraints to enforce.</p>
<p>The result is a single coherent surface — index DDL, constraint DDL,
catalog-backed scans, full-text and vector query procedures — wired
through the parser, store, optimizer, executor, WAL, and snapshots.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="not-a-shift-away-from-schema-free-graphs">Not a shift away from schema-free graphs<a href="https://loradb.com/blog/loradb-v0-9-indexes-and-constraints#not-a-shift-away-from-schema-free-graphs" class="hash-link" aria-label="Direct link to Not a shift away from schema-free graphs" title="Direct link to Not a shift away from schema-free graphs" translate="no">​</a></h2>
<p>LoraDB is still a schema-free property graph. Labels, relationship
types, and properties continue to appear when you write them. v0.9
does not introduce a mandatory schema.</p>
<p>What it introduces is a way for developers to be explicit about two
things:</p>
<ul>
<li class=""><em>Keep a secondary structure for this hot predicate</em> — that is an
index.</li>
<li class=""><em>Reject writes that violate this invariant</em> — that is a constraint.</li>
</ul>
<p>Both are opt-in. A graph that never issues a <code>CREATE INDEX</code> or
<code>CREATE CONSTRAINT</code> keeps the v0.8 behavior unchanged.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="index-ddl">Index DDL<a href="https://loradb.com/blog/loradb-v0-9-indexes-and-constraints#index-ddl" class="hash-link" aria-label="Direct link to Index DDL" title="Direct link to Index DDL" translate="no">​</a></h2>
<p>Index management is now part of the Cypher surface:</p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">CREATE INDEX user_email FOR (u:User) ON (u.email);
CREATE TEXT INDEX user_name FOR (u:User) ON (u.name);
CREATE POINT INDEX venue_location FOR (v:Venue) ON (v.location);
CREATE VECTOR INDEX movie_embedding FOR (m:Movie) ON (m.embedding)
OPTIONS {indexConfig: {`vector.dimensions`: 384, `vector.similarity_function`: 'cosine'}};
CREATE FULLTEXT INDEX article_search FOR (n:Article|Note) ON EACH [n.title, n.body];
CREATE INDEX rel_since FOR ()-[r:FOLLOWS]-() ON (r.since);

SHOW INDEXES;
SHOW VECTOR INDEXES;
DROP INDEX user_email IF EXISTS;</code></pre></div>
<p>The supported catalog kinds are RANGE, TEXT, POINT, LOOKUP, VECTOR, and
FULLTEXT. RANGE is the default for <code>CREATE INDEX</code>; TEXT and POINT
activate dedicated string and spatial candidate registries; LOOKUP
records label/type token indexes in the catalog; VECTOR records a kNN
configuration; FULLTEXT builds an inverted index over one or more
properties.</p>
<p><code>IF NOT EXISTS</code> is idempotent across both duplicate names and equivalent
index schemas. Duplicate names surface as <code>22N71</code>, equivalent schemas
as <code>22N70</code>, and dropping a missing index without <code>IF EXISTS</code> returns
<code>42N51</code>.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="constraint-ddl">Constraint DDL<a href="https://loradb.com/blog/loradb-v0-9-indexes-and-constraints#constraint-ddl" class="hash-link" aria-label="Direct link to Constraint DDL" title="Direct link to Constraint DDL" translate="no">​</a></h2>
<p>Schema constraints share the same surface:</p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">CREATE CONSTRAINT book_isbn FOR (b:Book) REQUIRE b.isbn IS UNIQUE;
CREATE CONSTRAINT author_name FOR (a:Author) REQUIRE a.name IS NOT NULL;
CREATE CONSTRAINT actor_fullname FOR (a:Actor)
REQUIRE (a.first, a.last) IS NODE KEY;
CREATE CONSTRAINT movie_title FOR (m:Movie)
REQUIRE m.title IS :: STRING | LIST&lt;STRING NOT NULL&gt;;

SHOW CONSTRAINTS;
DROP CONSTRAINT book_isbn IF EXISTS;</code></pre></div>
<p>The supported constraint family covers node and relationship
uniqueness, existence, node keys, relationship keys, and property type
constraints — including fixed-dimension VECTOR property types.
Creating a constraint validates existing data before committing the
catalog change; later writes are checked at mutation time.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="faster-scan-shapes">Faster scan shapes<a href="https://loradb.com/blog/loradb-v0-9-indexes-and-constraints#faster-scan-shapes" class="hash-link" aria-label="Direct link to Faster scan shapes" title="Direct link to Faster scan shapes" translate="no">​</a></h2>
<p>The optimizer now uses graph statistics and catalog state when it
compiles a query. Eligible scan-and-filter patterns can lower to
specialized operators:</p>
<ul>
<li class=""><code>NodeByPropertyScan</code> and <code>NodeByPropertyRangeScan</code></li>
<li class=""><code>NodeByTextScan</code></li>
<li class=""><code>NodeByPointScan</code></li>
<li class=""><code>RelByPropertyRangeScan</code></li>
<li class=""><code>RelByTextScan</code></li>
<li class=""><code>RelByPointScan</code></li>
</ul>
<p>That covers node and relationship predicates such as:</p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">MATCH (u:User) WHERE u.age &gt;= 18 AND u.age &lt; 65 RETURN u;
MATCH (u:User) WHERE u.name STARTS WITH 'Al' RETURN u;
MATCH (v:Venue)
WHERE geo.within_bbox(v.location, $southwest, $northeast)
RETURN v;
MATCH ()-[r:FOLLOWS]-&gt;() WHERE r.since &gt; 2020 RETURN r;</code></pre></div>
<p>The executor still refilters conservative candidate sets. TEXT indexes
use a trigram candidate path, and POINT indexes use spatial buckets, so
correctness does not depend on the index being exact.</p>
<p>Run the same query through <code>profile()</code> (shipped in v0.8) before and
after a <code>CREATE INDEX</code> to see the plan rewrite for itself.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="search-procedures">Search procedures<a href="https://loradb.com/blog/loradb-v0-9-indexes-and-constraints#search-procedures" class="hash-link" aria-label="Direct link to Search procedures" title="Direct link to Search procedures" translate="no">​</a></h2>
<p>Two catalog-backed procedure families are now exposed through the
limited built-in procedure dispatcher.</p>
<p>Full-text indexes support node and relationship scopes, multiple labels
or relationship types, and multiple indexed properties:</p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">CREATE FULLTEXT INDEX article_search
FOR (n:Article|Note) ON EACH [n.title, n.body]
OPTIONS {`fulltext.analyzer`: 'standard'};

CALL db.index.fulltext.queryNodes('article_search', 'graph powerful')
YIELD node, score;</code></pre></div>
<p>The standard analyzer lowercases text, splits on non-alphanumeric
boundaries, uses AND semantics across query terms, and scores by summed
term frequency. It returns rows in descending score order. It is
deliberately small: analyzer choice is currently limited to <code>standard</code>
and <code>simple</code>, both on the same synchronous maintenance path.</p>
<p>Vector indexes support node and relationship scopes with explicit
dimensions and either <code>cosine</code> or <code>euclidean</code> similarity:</p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">CREATE VECTOR INDEX movie_embedding FOR (m:Movie) ON (m.embedding)
OPTIONS {indexConfig: {`vector.dimensions`: 3, `vector.similarity_function`: 'cosine'}};

CALL db.index.vector.queryNodes('movie_embedding', 5, [1.0, 0.0, 0.0])
YIELD node, score;</code></pre></div>
<p>This is not an ANN engine yet. VECTOR indexes are catalog entries that
validate configuration and scope the query procedure; the current
implementation still uses a flat scan over matching entities and
returns the top <code>k</code> rows by score. The shape is stable; the speedup is
a later release.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="durability-and-snapshots">Durability and snapshots<a href="https://loradb.com/blog/loradb-v0-9-indexes-and-constraints#durability-and-snapshots" class="hash-link" aria-label="Direct link to Durability and snapshots" title="Direct link to Durability and snapshots" translate="no">​</a></h2>
<p>Index and constraint DDL travel through the same write path as graph
mutations. WAL payloads now encode <code>CreateIndex</code>, <code>DropIndex</code>,
<code>CreateConstraint</code>, and <code>DropConstraint</code> events, and recovery replays
them into the in-memory catalog before queries run.</p>
<p>Snapshots also carry both catalogs. The <code>LORACOL1</code> envelope remains
format version 2, and the snapshot body is now version 4: version 3
added the index-catalog trailer, and version 4 adds the
constraint-catalog trailer. Readers still accept older body formats,
loading older snapshots with empty index or constraint lists as
needed.</p>
<p>The snapshot and WAL codecs now use a small store-owned binary codec
for nested property values and catalog records. That keeps WAL and
snapshots aligned on the same catalog wire shape, including
VECTOR/FULLTEXT index definitions and constraint property-type
records.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="developer-facing-improvements">Developer-facing improvements<a href="https://loradb.com/blog/loradb-v0-9-indexes-and-constraints#developer-facing-improvements" class="hash-link" aria-label="Direct link to Developer-facing improvements" title="Direct link to Developer-facing improvements" translate="no">​</a></h2>
<p>Planner APIs now accept <code>GraphStats</code>, and the plan cache fingerprints
catalog and cardinality state so adding or dropping an index
invalidates stale plans. That means a query explained before
<code>CREATE INDEX</code> can recompile into an indexed scan immediately after the
catalog changes.</p>
<p>Read-only materialization also gained an optional native <code>parallel</code>
feature through Rayon. It is default-on for native builds and disabled
for WASM-style consumers through <code>default-features = false</code>.</p>
<p><code>SHOW INDEXES</code> now accepts type filters such as <code>SHOW RANGE INDEXES</code>,
<code>SHOW FULLTEXT INDEXES</code>, and <code>SHOW VECTOR INDEXES</code>. <code>SHOW INDEXES</code> and
<code>SHOW CONSTRAINTS</code> also accept a YIELD-anchored projection tail:</p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">SHOW INDEXES
YIELD name, type, entityType
WHERE type = 'FULLTEXT'
RETURN name
ORDER BY name;</code></pre></div>
<p>Benchmark coverage was reshaped around intent:</p>
<ul>
<li class=""><code>query_implementations</code> mirrors integration-test feature areas;</li>
<li class=""><code>index_acceleration</code> compares indexed and unindexed RANGE/TEXT
scenarios;</li>
<li class="">existing <code>scale</code>, <code>realistic</code>, <code>wal</code>, <code>concurrent</code>, and
<code>concurrency_guard</code> suites remain workload-specific tools.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="breaking-changes-and-migration-notes">Breaking changes and migration notes<a href="https://loradb.com/blog/loradb-v0-9-indexes-and-constraints#breaking-changes-and-migration-notes" class="hash-link" aria-label="Direct link to Breaking changes and migration notes" title="Direct link to Breaking changes and migration notes" translate="no">​</a></h2>
<p>For Rust callers of <code>lora-compiler</code>, <code>Compiler::compile</code> now requires a
<code>&amp;GraphStats</code> argument:</p>
<div class="language-rust codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-rust codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token keyword">let</span><span class="token plain"> compiled </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">Compiler</span><span class="token punctuation">::</span><span class="token function">compile</span><span class="token punctuation">(</span><span class="token operator">&amp;</span><span class="token plain">resolved</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token operator">&amp;</span><span class="token plain">store</span><span class="token punctuation">.</span><span class="token function">graph_stats</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></div></code></pre></div></div>
<p>Use <code>GraphStats::default()</code> when compiling outside a store-backed
runtime.</p>
<p>Snapshot writers now emit the newer <code>LORACOL1</code> envelope/body
combination. Current readers accept the previous body version, but
older binaries should not be expected to read snapshots written after
the index and constraint catalog trailers landed.</p>
<p>There are no data-model migrations for normal users. Existing graphs
remain schema-free; add indexes where predicates are hot enough to
justify the extra memory, and add constraints only for invariants you
want enforced on every matching write.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="notable-fixes">Notable fixes<a href="https://loradb.com/blog/loradb-v0-9-indexes-and-constraints#notable-fixes" class="hash-link" aria-label="Direct link to Notable fixes" title="Direct link to Notable fixes" translate="no">​</a></h2>
<ul>
<li class="">Plan-cache invalidation now accounts for catalog/cardinality changes.</li>
<li class="">Index and constraint catalog DDL survives WAL crash recovery.</li>
<li class="">TEXT and POINT indexes track property updates and label/type
membership changes.</li>
<li class="">FULLTEXT indexes backfill existing data and track property
updates/removals.</li>
<li class="">Constraint-owned backing indexes cannot be dropped directly; use
<code>DROP CONSTRAINT</code> so the catalogs stay in sync.</li>
<li class="">Indexed scans preserve already-bound node and relationship variables
instead of rebinding them.</li>
<li class="">Regex predicates cache compiled patterns per thread, avoiding
repeated compilation for row-by-row <code>=~</code> filters.</li>
<li class="">Numeric conversion helpers now reject non-finite or out-of-range
float-to-int conversions instead of silently casting.</li>
<li class="">Ruby binding calls release the GVL more defensively and surface
engine panics as query errors.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="how-v09-fits-the-journey">How v0.9 fits the journey<a href="https://loradb.com/blog/loradb-v0-9-indexes-and-constraints#how-v09-fits-the-journey" class="hash-link" aria-label="Direct link to How v0.9 fits the journey" title="Direct link to How v0.9 fits the journey" translate="no">​</a></h2>
<p>v0.5 made the engine stream. v0.6 made persistence feel like a system.
v0.7 was a process release. v0.8 made the planner and executor
observable.</p>
<p>v0.9 closes the last gap before the planner stops being a black box:
the catalog. Indexes were already inferable from the code; constraints
were not enforceable at all. v0.9 gives both a stable surface — Cypher
DDL, WAL events, snapshot trailers, and <code>SHOW</code> introspection — that
every binding inherits without per-language plumbing.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="still-open">Still open<a href="https://loradb.com/blog/loradb-v0-9-indexes-and-constraints#still-open" class="hash-link" aria-label="Direct link to Still open" title="Direct link to Still open" translate="no">​</a></h2>
<p>This work does not add ANN structures for vector search, custom
full-text analyzers, a full-text query language, general
<code>CALL</code>/<code>RETURN</code> procedure pipelines, or sorted-index <code>ORDER BY</code>
planning. Composite RANGE indexes are accepted and visible in
<code>SHOW INDEXES</code>, but current optimizer rewrites target single-property
predicates. Those are the natural extensions of the v0.9 shape, not
prerequisites for it.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="read-next">Read next<a href="https://loradb.com/blog/loradb-v0-9-indexes-and-constraints#read-next" class="hash-link" aria-label="Direct link to Read next" title="Direct link to Read next" translate="no">​</a></h2>
<ul>
<li class=""><a class="" href="https://loradb.com/docs/queries/indexes">Indexes — Cypher reference</a></li>
<li class=""><a class="" href="https://loradb.com/docs/queries/constraints">Constraints — Cypher reference</a></li>
<li class=""><a class="" href="https://loradb.com/docs/data-types/vectors">Vector values and similarity functions</a></li>
<li class=""><a class="" href="https://loradb.com/docs/limitations">Limitations</a></li>
</ul>
<p>v0.9 is the release where LoraDB's planner stops guessing at structure
that already exists in the application's head.</p>]]></content:encoded>
            <category>Release notes</category>
            <category>Announcement</category>
            <category>Performance</category>
            <category>Architecture</category>
            <category>Cypher</category>
        </item>
        <item>
            <title><![CDATA[LoraDB v0.8: explain, profile, and faster bindings]]></title>
            <link>https://loradb.com/blog/loradb-v0-8-explain-profile</link>
            <guid>https://loradb.com/blog/loradb-v0-8-explain-profile</guid>
            <pubDate>Tue, 05 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[LoraDB v0.8 introduces first-class explain() and profile() across every binding and the HTTP server, plus quieter binding speedups for bulk reads.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="LoraDB v0.8 — explain() compiles, profile() runs, the plan tree is the source of truth." src="https://loradb.com/assets/images/loradb-v0-8-explain-profile-header-7bcb4dff3026931adffcd6a7b0be92e6.png" width="1280" height="400" class="img__Ss2"></p>
<p>LoraDB v0.8 is a diagnostics release.</p>
<p>Until now, the only honest answer to "why is this query slow?" was
"read the executor source." v0.8 changes that. Every binding — Rust,
Node, WASM, Python, Go, Ruby, FFI — and the HTTP server now expose
<code>explain</code> and <code>profile</code> as first-class methods, returning the same
plan tree the engine actually compiles and runs.</p>
<p>The release also includes binding-level speedups for bulk reads. That
work is not the headline. The headline is that LoraDB queries are no
longer opaque.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-explain-and-profile-are">What <code>explain</code> and <code>profile</code> are<a href="https://loradb.com/blog/loradb-v0-8-explain-profile#what-explain-and-profile-are" class="hash-link" aria-label="Direct link to what-explain-and-profile-are" title="Direct link to what-explain-and-profile-are" translate="no">​</a></h2>
<p>They are two separate calls with two different contracts.</p>
<p><code>explain(query, params?)</code> parses, analyzes, and compiles the query and
returns the plan that <em>would</em> run. The executor is never invoked. Even
mutating queries — <code>CREATE</code>, <code>MERGE</code>, <code>SET</code>, <code>DELETE</code>, <code>REMOVE</code> — are
safe to pass: they return a plan and leave the graph untouched. Use
this to inspect what the planner decided before you commit to running
it.</p>
<p><code>profile(query, params?)</code> runs the query for real and returns the
plan plus runtime metrics. Mutating queries produce the same side
effects they would from <code>execute</code>: WAL is written, snapshots observe
the commit, the live store advances. <code>profile</code> is a measurement tool,
not a sandbox.</p>
<p>In Node:</p>
<div class="language-ts codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-ts codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token keyword">const</span><span class="token plain"> plan </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">explain</span><span class="token punctuation">(</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token string">"MATCH (p:Person) WHERE p.name = $name RETURN p"</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token punctuation">{</span><span class="token plain"> name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">'Ada'</span><span class="token plain"> </span><span class="token punctuation">}</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token plain">plan</span><span class="token punctuation">.</span><span class="token plain">shape</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain">          </span><span class="token comment">// "readOnly" or "mutating"</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token plain">plan</span><span class="token punctuation">.</span><span class="token plain">resultColumns</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain">  </span><span class="token comment">// ["p"]</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token plain">plan</span><span class="token punctuation">.</span><span class="token plain">tree</span><span class="token punctuation">.</span><span class="token plain">operator</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain">  </span><span class="token comment">// top-level physical operator</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> profile </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">profile</span><span class="token punctuation">(</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token string">"MATCH (p:Person) WHERE p.name = $name RETURN p"</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token punctuation">{</span><span class="token plain"> name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">'Ada'</span><span class="token plain"> </span><span class="token punctuation">}</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token plain">profile</span><span class="token punctuation">.</span><span class="token plain">metrics</span><span class="token punctuation">.</span><span class="token plain">totalElapsedNs</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token plain">profile</span><span class="token punctuation">.</span><span class="token plain">metrics</span><span class="token punctuation">.</span><span class="token plain">totalRows</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token plain">profile</span><span class="token punctuation">.</span><span class="token plain">metrics</span><span class="token punctuation">.</span><span class="token plain">perOperator</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></div></code></pre></div></div>
<p>The same shape is available over HTTP at <code>POST /explain</code> and
<code>POST /profile</code>, and from Python, Ruby, Go, WASM, and the FFI. The
JSON envelope is identical across every surface.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-the-plan-tree-actually-says">What the plan tree actually says<a href="https://loradb.com/blog/loradb-v0-8-explain-profile#what-the-plan-tree-actually-says" class="hash-link" aria-label="Direct link to What the plan tree actually says" title="Direct link to What the plan tree actually says" translate="no">​</a></h2>
<p><code>explain</code> returns a tree, not a string. Each node carries a stable
<code>id</code>, an <code>operator</code> label (<code>Projection</code>, <code>Filter</code>, <code>NodeByLabelScan</code>,
…), opaque human-readable <code>details</code>, and its children:</p>
<div class="language-json codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-json codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token punctuation">{</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token property">"query"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"MATCH (p:Person) WHERE p.name = $name RETURN p"</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token property">"shape"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"readOnly"</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token property">"resultColumns"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation">[</span><span class="token string">"p"</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token property">"tree"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token property">"operator"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"Projection"</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token property">"details"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> </span><span class="token property">"items"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"p"</span><span class="token plain"> </span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token property">"children"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation">[</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">      </span><span class="token punctuation">{</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">        </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">2</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">        </span><span class="token property">"operator"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"Filter"</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">        </span><span class="token property">"details"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> </span><span class="token property">"predicate"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"..."</span><span class="token plain"> </span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">        </span><span class="token property">"children"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation">[</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">          </span><span class="token punctuation">{</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">            </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">            </span><span class="token property">"operator"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"NodeByLabelScan"</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">            </span><span class="token property">"details"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> </span><span class="token property">"var"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"v0"</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token property">"labels"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"Person"</span><span class="token plain"> </span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">            </span><span class="token property">"children"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">          </span><span class="token punctuation">}</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">        </span><span class="token punctuation">]</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">      </span><span class="token punctuation">}</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token punctuation">]</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token punctuation">}</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token punctuation">}</span><br></div></code></pre></div></div>
<p><code>shape</code> is <code>"readOnly"</code> or <code>"mutating"</code> — a property of the plan, not
a guess from the query string. <code>resultColumns</code> is the projection order
the engine will produce. <code>details</code> is for humans; do not parse it.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-profile-adds">What <code>profile</code> adds<a href="https://loradb.com/blog/loradb-v0-8-explain-profile#what-profile-adds" class="hash-link" aria-label="Direct link to what-profile-adds" title="Direct link to what-profile-adds" translate="no">​</a></h2>
<p><code>profile</code> decorates the same tree with measurements:</p>
<div class="language-json codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-json codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token punctuation">{</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token property">"plan"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> </span><span class="token string">"...same shape as /explain..."</span><span class="token plain"> </span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token property">"metrics"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token property">"totalElapsedNs"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">124500</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token property">"totalRows"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token property">"mutated"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token property">"perOperator"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">      </span><span class="token property">"1"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> </span><span class="token property">"rows"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">5</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token property">"elapsedNs"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">18200</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token property">"nextCalls"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">6</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token property">"dbHits"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">      </span><span class="token property">"2"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> </span><span class="token property">"rows"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">4</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token property">"elapsedNs"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">21100</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token property">"nextCalls"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">5</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token property">"dbHits"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">      </span><span class="token property">"3"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> </span><span class="token property">"rows"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">4</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token property">"elapsedNs"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">24400</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token property">"nextCalls"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">5</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token property">"dbHits"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token punctuation">}</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token punctuation">}</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token punctuation">}</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token punctuation">}</span><br></div></code></pre></div></div>
<p><code>perOperator</code> keys match <code>tree[*].id</code>. Per-operator <code>elapsedNs</code> is
inclusive of descendants — the "operator + everything below it" view
that matches what is visually surprising when reading a profile.
<code>dbHits</code> is reserved for a future phase and reads <code>0</code> today; saying
that out loud is the point of v0.8.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-honest-boundary">The honest boundary<a href="https://loradb.com/blog/loradb-v0-8-explain-profile#the-honest-boundary" class="hash-link" aria-label="Direct link to The honest boundary" title="Direct link to The honest boundary" translate="no">​</a></h2>
<p>A few things <code>profile</code> is <strong>not</strong> in v0.8:</p>
<ul>
<li class="">It is not a query optimizer. There is no cost model yet;
<code>estimatedRows</code> is <code>null</code>.</li>
<li class="">It does not sandbox writes. Mutating queries mutate.</li>
<li class="">It does not page or sample. The full plan and metrics ride on a
single response.</li>
<li class=""><code>dbHits</code> is reserved, not measured. v0.8 reports <code>0</code> rather than a
fabricated number.</li>
</ul>
<p>That last one matters more than it sounds. A diagnostic surface that
quietly invents a metric is worse than one that admits the metric is
not implemented.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="a-short-word-on-bindings">A short word on bindings<a href="https://loradb.com/blog/loradb-v0-8-explain-profile#a-short-word-on-bindings" class="hash-link" aria-label="Direct link to A short word on bindings" title="Direct link to A short word on bindings" translate="no">​</a></h2>
<p>v0.8 also includes bulk-buffer changes in the Node, WASM, Go, and
FFI bindings. Bulk reads now return a single contiguous binary buffer
that the host language decodes locally, instead of crossing the FFI
boundary row-by-row. Microbenchmarks show roughly 2–4× faster reads
for queries that return many rows.</p>
<p>That work shipped quietly because it doesn't change any API. The
binding signatures are the same; the wire is just narrower. If your
code calls <code>execute</code> and reads many rows, you should see it without
doing anything.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="how-v08-fits-the-journey">How v0.8 fits the journey<a href="https://loradb.com/blog/loradb-v0-8-explain-profile#how-v08-fits-the-journey" class="hash-link" aria-label="Direct link to How v0.8 fits the journey" title="Direct link to How v0.8 fits the journey" translate="no">​</a></h2>
<p>v0.5 made the engine stream. v0.6 made persistence feel like a
system. v0.7 was a process release.</p>
<p>v0.8 closes a different gap: the engine has been streaming and
persisting for two releases without giving callers a way to see what
it actually did. <code>explain</code> and <code>profile</code> make the planner and the
executor observable from every surface that runs a query.</p>
<p>The next steps follow from there. Per-operator <code>dbHits</code>, an actual
cost model, plan-stability assertions for tests, and an HTTP <code>params</code>
field on <code>/query</code> are all natural extensions of the same shape.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="read-next">Read next<a href="https://loradb.com/blog/loradb-v0-8-explain-profile#read-next" class="hash-link" aria-label="Direct link to Read next" title="Direct link to Read next" translate="no">​</a></h2>
<ul>
<li class=""><a class="" href="https://loradb.com/docs/api/http#post-explain">HTTP API reference — <code>/explain</code> and <code>/profile</code></a></li>
<li class=""><a class="" href="https://loradb.com/docs/getting-started/node#explain-and-profile">Node binding — explain and profile</a></li>
<li class=""><a class="" href="https://loradb.com/docs/limitations">Limitations</a></li>
</ul>
<p>v0.8 is the release where LoraDB stops being a black box at the query
boundary.</p>]]></content:encoded>
            <category>Release notes</category>
            <category>Announcement</category>
            <category>Performance</category>
        </item>
        <item>
            <title><![CDATA[Building LoraDB with Claude and Codex]]></title>
            <link>https://loradb.com/blog/building-loradb-with-ai</link>
            <guid>https://loradb.com/blog/building-loradb-with-ai</guid>
            <pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How Claude and Codex fit into LoraDB's engineering workflow: repository work, documentation, releases, and product direction, with the code remaining the source of truth.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Built with Claude and Codex — the code stays the source of truth." src="https://loradb.com/assets/images/building-loradb-with-ai-header-d4763d23e58ab3f327d63911db68d29c.png" width="1280" height="400" class="img__Ss2"></p>
<p>I use Claude and Codex to build LoraDB.</p>
<p>That is not a positioning statement or a shortcut around the work. It is simply part of how the project gets built.</p>
<p>Claude and Codex help with different parts of the process: reading code, checking assumptions, shaping docs, reviewing release work, and testing whether the project is saying things the implementation can actually support. They are useful tools, but they are not the source of truth.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-practical-version">The Practical Version<a href="https://loradb.com/blog/building-loradb-with-ai#the-practical-version" class="hash-link" aria-label="Direct link to The Practical Version" title="Direct link to The Practical Version" translate="no">​</a></h2>
<p>The practical version is fairly ordinary.</p>
<p>Claude is useful inside the repository. It can read files, follow code paths, run commands, patch docs, check links, find stale claims, and keep track of release work that is easy to get almost right.</p>
<p>Codex is useful around the repository. It helps with structure, narrative, product direction, and the question of whether a phrase like "production-grade" is being earned or just repeated.</p>
<p>I use both, and I reject both often. Their value is not that they are always right. Their value is that they make it easier to inspect the project from several angles before deciding what should change.</p>
<p><img decoding="async" loading="lazy" alt="The loop: Claude and Codex propose, the proposal is verified against the implementation, most are rejected, only verified work reaches the code." src="https://loradb.com/assets/images/building-loradb-with-ai-loop-31039cd594d92aeebf72fbe795d1e8f5.png" width="960" height="400" class="img__Ss2"></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="why-this-matters-for-loradb">Why This Matters For LoraDB<a href="https://loradb.com/blog/building-loradb-with-ai#why-this-matters-for-loradb" class="hash-link" aria-label="Direct link to Why This Matters For LoraDB" title="Direct link to Why This Matters For LoraDB" translate="no">​</a></h2>
<p>LoraDB is at an awkward and interesting stage.</p>
<p>It is no longer just a small in-memory graph experiment. It has a query pipeline, a graph store, vectors, snapshots, WAL, checkpointing, streaming, bindings, an HTTP server, and a growing documentation site.</p>
<p>But it is also not the database I want it to become yet.</p>
<p>The direction is a production-grade persistent and concurrent graph database.</p>
<p>That requires precision. It means knowing what the query engine promises, where state lives, how recovery works, which surfaces are stable, and which are still experimental. It also means being clear about where concurrency is real today, where it is conservative, and where it is still future work.</p>
<p>AI helps because it can keep more of that context visible while I work through the next change.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-risk">The Risk<a href="https://loradb.com/blog/building-loradb-with-ai#the-risk" class="hash-link" aria-label="Direct link to The Risk" title="Direct link to The Risk" translate="no">​</a></h2>
<p>The risk is fluency.</p>
<p>It can explain a feature that does not exist. It can make a limitation sound like an implementation detail. It can blur "we want this" into "we have this." It can turn a database roadmap into a brochure if you let it.</p>
<p>So the rule is simple: the code wins.</p>
<p>If Claude writes a claim, the implementation has to back it. If Codex helps shape a post, the post still has to respect the current product. If something is work in progress, it should be called work in progress.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="where-it-helps">Where It Helps<a href="https://loradb.com/blog/building-loradb-with-ai#where-it-helps" class="hash-link" aria-label="Direct link to Where It Helps" title="Direct link to Where It Helps" translate="no">​</a></h2>
<p>AI is useful for breadth.</p>
<p>LoraDB spans several kinds of work:</p>
<ul>
<li class="">Rust engine design;</li>
<li class="">query semantics;</li>
<li class="">persistence and recovery;</li>
<li class="">concurrency boundaries;</li>
<li class="">HTTP behavior;</li>
<li class="">language bindings;</li>
<li class="">documentation;</li>
<li class="">release process;</li>
<li class="">product narrative.</li>
</ul>
<p>A human can reason about all of that, but context switching has a cost. AI helps reduce that cost by making it faster to ask the obvious follow-up questions.</p>
<p>Claude is good at the repository question: where is this in the code?</p>
<p>Codex is good at the product question: what does this mean for the user?</p>
<p><img decoding="async" loading="lazy" alt="Two questions, one project: Codex asks where this is in the code, Claude asks what it means for the user. Direction, merge, and publish stay with the human owner." src="https://loradb.com/assets/images/building-loradb-with-ai-breadth-c1de1a5c1d050979dd99cf419756cb8a.png" width="960" height="400" class="img__Ss2"></p>
<p>Those two questions catch a lot of weak spots.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-still-has-to-be-decided-by-me">What Still Has To Be Decided By Me<a href="https://loradb.com/blog/building-loradb-with-ai#what-still-has-to-be-decided-by-me" class="hash-link" aria-label="Direct link to What Still Has To Be Decided By Me" title="Direct link to What Still Has To Be Decided By Me" translate="no">​</a></h2>
<p>I own the direction.</p>
<p>I own the decision to merge or not merge, to publish or not publish, to call something stable or experimental, to make the next release about a feature or about structure.</p>
<p>That part cannot be outsourced.</p>
<p>AI can speed up parts of the work, but it does not decide what LoraDB should become.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="where-loradb-is-heading">Where LoraDB Is Heading<a href="https://loradb.com/blog/building-loradb-with-ai#where-loradb-is-heading" class="hash-link" aria-label="Direct link to Where LoraDB Is Heading" title="Direct link to Where LoraDB Is Heading" translate="no">​</a></h2>
<p>The database I want is one that can be trusted when things go wrong.</p>
<p>Not just when a demo query runs, but when a process dies, a WAL has to replay, a checkpoint is stale, two clients write, a stream stays open, or a binding exposes a smaller surface than Rust.</p>
<p>Claude and Codex help by making the project easier to inspect.</p>
<p>They do not make it correct.</p>
<p>They make it harder for me to avoid the places where correctness still needs work.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="why-say-this">Why Say This<a href="https://loradb.com/blog/building-loradb-with-ai#why-say-this" class="hash-link" aria-label="Direct link to Why Say This" title="Direct link to Why Say This" translate="no">​</a></h2>
<p>Because AI is part of the process, and it is better to be direct about that.</p>
<p>Because the way LoraDB is built affects the kind of project it becomes.</p>
<p>Because there is a serious version of AI-assisted engineering: use the tools, name the tools, verify the tools, and keep responsibility with the person building the system.</p>
<p><img decoding="async" loading="lazy" alt="The rule: use the tools, name the tools, verify the tools." src="https://loradb.com/assets/images/building-loradb-with-ai-rule-21bb2f1a697a36d5bb787e7069ab306b.png" width="960" height="120" class="img__Ss2"></p>
<p>LoraDB is being built with Claude and Codex as part of the workflow.</p>
<p>That does not make the database trustworthy. It helps me find the places where the code, docs, and expectations do not line up. LoraDB still has to earn trust through implementation, tests, and boring operational behavior.</p>]]></content:encoded>
            <category>Founder notes</category>
            <category>AI &amp; agents</category>
            <category>Architecture</category>
            <category>Design</category>
        </item>
        <item>
            <title><![CDATA[LoraDB v0.7: AI-assisted engineering, honestly]]></title>
            <link>https://loradb.com/blog/loradb-v0-7-ai-assisted-engineering</link>
            <guid>https://loradb.com/blog/loradb-v0-7-ai-assisted-engineering</guid>
            <pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[LoraDB v0.7 is a process release about using Claude and Codex across the project: code review, refactoring, documentation, release work, and product direction without pretending AI owns the engineering.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="LoraDB v0.7 — AI-assisted engineering, honestly: use, name, verify the tools." src="https://loradb.com/assets/images/loradb-v0-7-ai-assisted-engineering-header-384ad7a211f521f8fd215c70f27c3048.png" width="1280" height="400" class="img__Ss2"></p>
<p>LoraDB v0.7 is about how the project is being built.</p>
<p>Claude and Codex are now part of the LoraDB engineering loop. Not as a mascot,
not as a replacement team, and not as a way to avoid responsibility. They are
used across the project: code review, refactoring, documentation, release work,
architecture pressure, and product-direction thinking.</p>
<p>This release says that out loud.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-ai-is-doing-in-loradb">What AI Is Doing In LoraDB<a href="https://loradb.com/blog/loradb-v0-7-ai-assisted-engineering#what-ai-is-doing-in-loradb" class="hash-link" aria-label="Direct link to What AI Is Doing In LoraDB" title="Direct link to What AI Is Doing In LoraDB" translate="no">​</a></h2>
<p>AI is being used across the full scope of the project:</p>
<ul>
<li class="">reading Rust code and tracing behavior across crates;</li>
<li class="">auditing documentation against implementation;</li>
<li class="">helping split large ideas into smaller changes;</li>
<li class="">reviewing error boundaries and API surfaces;</li>
<li class="">drafting and revising release notes;</li>
<li class="">checking whether examples still match the code;</li>
<li class="">running local validation commands;</li>
<li class="">comparing architecture claims against source files;</li>
<li class="">helping reason about where persistence and concurrency should go next.</li>
</ul>
<p>Claude does most of the code work. It inspects the codebase, edits files,
runs tests, builds the docs site, and shapes the next change inside the
repository.</p>
<p>Codex sits on the other side of the loop, as the reviewer and the
documentarian. It checks code changes against intent, audits the docs against
the implementation, drafts and revises release notes, and catches the small
mismatches that make a database project feel less trustworthy.</p>
<p>Those are different jobs. Both are useful.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-ai-is-not-doing">What AI Is Not Doing<a href="https://loradb.com/blog/loradb-v0-7-ai-assisted-engineering#what-ai-is-not-doing" class="hash-link" aria-label="Direct link to What AI Is Not Doing" title="Direct link to What AI Is Not Doing" translate="no">​</a></h2>
<p>AI is not the source of truth.</p>
<p>The source of truth is still the code, the tests, the build, and the judgment
of the maintainer.</p>
<p>AI does not decide that a storage model is correct. It can help inspect it.</p>
<p>AI does not make a persistence feature production-grade. It can help reveal
where the docs overstate the guarantee.</p>
<p>AI does not own a release. It can help keep the checklist from being sloppy.</p>
<p>AI does not remove the need to understand the database. It raises the cost of
not understanding it, because it makes it easier to ask the codebase the same
question from several angles.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="why-this-matters-for-a-database">Why This Matters For A Database<a href="https://loradb.com/blog/loradb-v0-7-ai-assisted-engineering#why-this-matters-for-a-database" class="hash-link" aria-label="Direct link to Why This Matters For A Database" title="Direct link to Why This Matters For A Database" translate="no">​</a></h2>
<p>Databases are promise machines.</p>
<p>They make promises about state, recovery, isolation, durability, query
semantics, and failure. If the words around those promises drift away from the
implementation, the project becomes dangerous in a quiet way.</p>
<p>That is why using AI here has to be unusually disciplined.</p>
<p>Claude and Codex are both capable of producing confident text about behavior
that does not exist. That is the risk. The useful pattern is the opposite:
make them search, compare, challenge, and verify.</p>
<p>In LoraDB, AI is valuable when it increases friction around overclaiming.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-whole-project-scope">The Whole Project Scope<a href="https://loradb.com/blog/loradb-v0-7-ai-assisted-engineering#the-whole-project-scope" class="hash-link" aria-label="Direct link to The Whole Project Scope" title="Direct link to The Whole Project Scope" translate="no">​</a></h2>
<p>The AI-assisted loop is not limited to documentation.</p>
<p>It touches the whole project:</p>
<ul>
<li class="">the query pipeline, where parser, analyzer, compiler, and executor behavior
must line up;</li>
<li class="">the graph store, where internal data structures and public semantics should
not be confused;</li>
<li class="">the database facade, where read/write behavior, transactions, and streams
need precise language;</li>
<li class="">the WAL and snapshot layers, where recovery claims must be backed by actual
code paths;</li>
<li class="">the HTTP server and bindings, where each surface exposes a slightly different
operational contract;</li>
<li class="">the docs site and blog, where user expectations are shaped;</li>
<li class="">the release process, where versions, lockfiles, builds, and posts all have to
move together.</li>
</ul>
<p>That breadth is exactly why AI is useful. It can keep a large amount of context
warm while the human maintainer decides what should actually change.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="steering-toward-production-grade-persistence-and-concurrency">Steering Toward Production-Grade Persistence And Concurrency<a href="https://loradb.com/blog/loradb-v0-7-ai-assisted-engineering#steering-toward-production-grade-persistence-and-concurrency" class="hash-link" aria-label="Direct link to Steering Toward Production-Grade Persistence And Concurrency" title="Direct link to Steering Toward Production-Grade Persistence And Concurrency" translate="no">​</a></h2>
<p>LoraDB is not finished.</p>
<p>The direction is clear, though: production-grade persistence and production-grade
concurrency.</p>
<p>Persistence means more than "there is a file." It means recovery paths that are
boring, WAL behavior that can be inspected, snapshot compatibility that is
documented, checkpoint semantics that are clear, and operational surfaces that
do not surprise people.</p>
<p>Concurrency means more than "some reads overlap." It means read behavior, write
serialization, transaction boundaries, stream lifetimes, and future
fine-grained coordination all have to be understandable.</p>
<p>Claude and Codex help with that by forcing more explicit structure. They help
turn "this feels right" into "where does the code prove it?"</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-honest-boundary">The Honest Boundary<a href="https://loradb.com/blog/loradb-v0-7-ai-assisted-engineering#the-honest-boundary" class="hash-link" aria-label="Direct link to The Honest Boundary" title="Direct link to The Honest Boundary" translate="no">​</a></h2>
<p>This release does not claim that AI makes LoraDB production-grade.</p>
<p>It claims something smaller and more useful: AI is now part of the way LoraDB is
being steered toward that goal.</p>
<p>Used badly, AI would make the project sound more complete than it is.</p>
<p>Used well, it makes the project more honest about what is implemented, what is
experimental, and what still needs real engineering.</p>
<p>v0.7 is a marker for that working style.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="read-next">Read Next<a href="https://loradb.com/blog/loradb-v0-7-ai-assisted-engineering#read-next" class="hash-link" aria-label="Direct link to Read Next" title="Direct link to Read Next" translate="no">​</a></h2>
<ul>
<li class=""><a class="" href="https://loradb.com/docs/why">Why LoraDB</a></li>
<li class=""><a class="" href="https://loradb.com/docs/limitations">Limitations</a></li>
<li class=""><a class="" href="https://loradb.com/docs/snapshot">Snapshots</a></li>
<li class=""><a class="" href="https://loradb.com/docs/wal">WAL and checkpoints</a></li>
<li class=""><a class="" href="https://loradb.com/docs/concepts/graph-model">Graph model</a></li>
</ul>
<p>The goal is not to hide AI usage. The goal is to use it plainly, carefully,
and in service of a better database.</p>]]></content:encoded>
            <category>Release notes</category>
            <category>Announcement</category>
            <category>Architecture</category>
            <category>AI &amp; agents</category>
        </item>
        <item>
            <title><![CDATA[The LoraDB release journey so far]]></title>
            <link>https://loradb.com/blog/loradb-release-journey</link>
            <guid>https://loradb.com/blog/loradb-release-journey</guid>
            <pubDate>Sun, 03 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A narrative map of LoraDB from public v0.1 through vectors, snapshots, WAL recovery, streaming execution, columnar checkpoints, and the current performance/concurrency work.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="The LoraDB release journey — v0.1 through v0.6 milestones." src="https://loradb.com/assets/images/loradb-release-journey-header-2b1273f449e63a2770a824a99eda3746.png" width="1280" height="400" class="img__Ss2"></p>
<p>LoraDB has moved quickly since the public release, so it is worth
stepping back from the version numbers and looking at the journey.</p>
<p>The individual posts tell the detail of each release. This one is the
map: why the releases landed in this order, what each one proved, and
how the current work fits the long arc from "fast local graph engine" to
"database people can trust in the product loop."</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="v01-make-the-core-public">v0.1: Make The Core Public<a href="https://loradb.com/blog/loradb-release-journey#v01-make-the-core-public" class="hash-link" aria-label="Direct link to v0.1: Make The Core Public" title="Direct link to v0.1: Make The Core Public" translate="no">​</a></h2>
<p>The first public release made the bet visible.</p>
<p>LoraDB shipped as a Rust in-memory graph database with a Cypher-shaped
query engine, an HTTP server, and early bindings. The important part was
not that every database feature existed. It was that the core pipeline was
there and readable:</p>
<ul>
<li class="">parse;</li>
<li class="">analyze;</li>
<li class="">plan;</li>
<li class="">execute;</li>
<li class="">store graph values;</li>
<li class="">return results through Rust, HTTP, and bindings.</li>
</ul>
<p>That release set the product tone. LoraDB would earn trust locally before
asking anyone to trust a hosted platform.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="v02-put-vectors-inside-the-graph">v0.2: Put Vectors Inside The Graph<a href="https://loradb.com/blog/loradb-release-journey#v02-put-vectors-inside-the-graph" class="hash-link" aria-label="Direct link to v0.2: Put Vectors Inside The Graph" title="Direct link to v0.2: Put Vectors Inside The Graph" translate="no">​</a></h2>
<p>The second release added first-class <code>VECTOR</code> values.</p>
<p>That was not a pivot into being a vector database. It was the opposite:
an argument that embeddings are more useful when they live next to labels,
properties, and relationships.</p>
<p>Similarity can find candidates. The graph can explain, filter, and rank
them with context. v0.2 made that possible in one value model and one
Cypher surface, across the bindings.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="v03-save-the-graph">v0.3: Save The Graph<a href="https://loradb.com/blog/loradb-release-journey#v03-save-the-graph" class="hash-link" aria-label="Direct link to v0.3: Save The Graph" title="Direct link to v0.3: Save The Graph" translate="no">​</a></h2>
<p>v0.3 added manual snapshots.</p>
<p>That sounds small until you look at the trust boundary it creates. Before
snapshots, LoraDB was a fast in-memory graph that disappeared with the
process. After snapshots, a developer could carry graph state across
sessions, ship a seed file, back up a notebook, or restore a service from
one artifact.</p>
<p>Snapshots were not marketed as full durability. They were deliberately
point-in-time persistence. That honesty mattered because the snapshot
contract became the base for checkpoints later.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="v04-recover-committed-writes">v0.4: Recover Committed Writes<a href="https://loradb.com/blog/loradb-release-journey#v04-recover-committed-writes" class="hash-link" aria-label="Direct link to v0.4: Recover Committed Writes" title="Direct link to v0.4: Recover Committed Writes" translate="no">​</a></h2>
<p>v0.4 added the WAL.</p>
<p>This is where persistence became continuous on filesystem-backed
surfaces. Committed writes could be replayed after restart, torn log tails
could be handled, and the server gained admin routes for checkpointing and
WAL management.</p>
<p>The story changed from "save when you decide" to "recover what committed."
Snapshots stayed important. They became the thing a WAL can checkpoint
against.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="v05-stream-the-query-path">v0.5: Stream The Query Path<a href="https://loradb.com/blog/loradb-release-journey#v05-stream-the-query-path" class="hash-link" aria-label="Direct link to v0.5: Stream The Query Path" title="Direct link to v0.5: Stream The Query Path" translate="no">​</a></h2>
<p>v0.5 shifted attention from durability to flow.</p>
<p>The pull-based executor, owned query streams, client stream APIs, property
indexes, and memory indexing work all point at the same product feeling:
the database should not force large graph work through one oversized
materialized response.</p>
<p>That is a necessary step for a local-first graph engine. If LoraDB is
going to sit close to applications, it has to hand results to those
applications in a shape they can process naturally.</p>
<p>The v0.5 patch releases also tightened WAL persistence edges. That is how
trust accumulates: feature, use, fix, repeat.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="v06-turn-persistence-into-a-system">v0.6: Turn Persistence Into A System<a href="https://loradb.com/blog/loradb-release-journey#v06-turn-persistence-into-a-system" class="hash-link" aria-label="Direct link to v0.6: Turn Persistence Into A System" title="Direct link to v0.6: Turn Persistence Into A System" translate="no">​</a></h2>
<p>v0.6 upgraded snapshots into the current columnar <code>LORACOL1</code> format and
connected them more tightly to WAL checkpoints.</p>
<p>This is the release where the persistence staircase became easy to
explain:</p>
<ul>
<li class="">in-memory for the fastest local loop;</li>
<li class="">snapshots for point-in-time graph artifacts;</li>
<li class="">WAL for committed write recovery;</li>
<li class="">checkpoint snapshots to bound replay.</li>
</ul>
<p>The docs were refreshed around that model because old release notes can
become dangerous if they teach old API names. The posts now preserve the
history while pointing readers at the current shape.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="current-work-make-the-hot-path-concurrent">Current Work: Make The Hot Path Concurrent<a href="https://loradb.com/blog/loradb-release-journey#current-work-make-the-hot-path-concurrent" class="hash-link" aria-label="Direct link to Current Work: Make The Hot Path Concurrent" title="Direct link to Current Work: Make The Hot Path Concurrent" translate="no">​</a></h2>
<p>The latest commits after v0.6 continue the same thread.</p>
<p>Plan caching reduces repeated parse/analyze/compile work. Lock-free reads
via an <code>ArcSwap</code> snapshot store make reads cheaper under concurrency.
Arc-wrapped records make snapshot clones less expensive. Optimistic writes,
per-record validation, a lock table, mutation write sets, and merge replay
move the write path toward finer-grained concurrency.</p>
<p>The refactors that followed are not cosmetic. Splitting large modules in
the store, database, WAL, snapshot, parser, analyzer, executor, server, and
bindings makes the next round of performance work less risky. A system gets
faster when its parts are easier to reason about.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-pattern">The Pattern<a href="https://loradb.com/blog/loradb-release-journey#the-pattern" class="hash-link" aria-label="Direct link to The Pattern" title="Direct link to The Pattern" translate="no">​</a></h2>
<p>The release pattern is now visible:</p>
<ol>
<li class=""><strong>Make the developer loop good.</strong> In-memory, readable, local,
Cypher-shaped.</li>
<li class=""><strong>Add the value model people need.</strong> Vectors belong in the graph when
context matters.</li>
<li class=""><strong>Add persistence one honest contract at a time.</strong> Snapshot first, WAL
second, checkpoints after both are clear.</li>
<li class=""><strong>Improve execution flow.</strong> Stream rows, index common lookups, avoid
unnecessary materialization.</li>
<li class=""><strong>Harden concurrency and structure.</strong> Make reads cheap, writes safer,
and internals easier to change without breaking the product surface.</li>
</ol>
<p>That pattern matters more than any single feature. It is how LoraDB keeps
the product journey coherent while the engine grows quickly.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-readers-should-take-away">What Readers Should Take Away<a href="https://loradb.com/blog/loradb-release-journey#what-readers-should-take-away" class="hash-link" aria-label="Direct link to What Readers Should Take Away" title="Direct link to What Readers Should Take Away" translate="no">​</a></h2>
<p>LoraDB is not trying to jump straight from a public repo to a hosted graph
cloud by announcement.</p>
<p>It is building the trust layers in order:</p>
<ul>
<li class="">can I understand it;</li>
<li class="">can I query it;</li>
<li class="">can I store the values my workload needs;</li>
<li class="">can I save and recover it;</li>
<li class="">can I stream results;</li>
<li class="">can it stay fast under concurrent use;</li>
<li class="">can someone else operate it for me later.</li>
</ul>
<p>That is the journey of LoraDB so far. The next releases should keep making
that journey easier to follow, not just longer.</p>]]></content:encoded>
            <category>Founder notes</category>
            <category>Release notes</category>
            <category>Architecture</category>
            <category>Performance</category>
        </item>
        <item>
            <title><![CDATA[LoraDB v0.6: columnar snapshots and managed WAL checkpoints]]></title>
            <link>https://loradb.com/blog/loradb-v0-6-columnar-checkpoints</link>
            <guid>https://loradb.com/blog/loradb-v0-6-columnar-checkpoints</guid>
            <pubDate>Wed, 29 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[LoraDB v0.6 upgrades persistence with the columnar LORACOL1 snapshot format, managed commit-count WAL checkpoints, and aligned binding APIs across Rust, Node, WASM, Python, Go, and Ruby.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="LoraDB v0.6 — columnar LORACOL1 snapshots and managed WAL checkpoints." src="https://loradb.com/assets/images/loradb-v0-6-columnar-checkpoints-header-31f5a0b81a3eff6e89910dcca1641e2e.png" width="1280" height="400" class="img__Ss2"></p>
<p>LoraDB v0.6 is a persistence hardening release.</p>
<p>v0.3 introduced snapshots. v0.4 introduced WAL recovery. v0.5 made the
engine stream results and tightened container-backed persistence. v0.6
brings those persistence pieces into a cleaner shape: columnar snapshots,
managed WAL checkpoints, and binding APIs that describe the same
operational model from every runtime.</p>
<p>The headline is simple: the persistence story is no longer "one file"
and "one log" as separate ideas. Snapshots are now the checkpoint
artifact the WAL can lean on.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-changed">What Changed<a href="https://loradb.com/blog/loradb-v0-6-columnar-checkpoints#what-changed" class="hash-link" aria-label="Direct link to What Changed" title="Direct link to What Changed" translate="no">​</a></h2>
<p>The short list:</p>
<ul>
<li class="">the current database snapshot format is now columnar <code>LORACOL1</code>;</li>
<li class="">snapshot envelopes carry a BLAKE3 checksum;</li>
<li class="">snapshot metadata supports compression and encryption options;</li>
<li class="">WAL checkpoints can stamp snapshots with a durable <code>walLsn</code> fence;</li>
<li class="">raw-WAL helpers can write managed snapshots after N committed
transactions;</li>
<li class="">binding APIs were aligned for WAL and snapshot operations;</li>
<li class="">historical persistence docs were refreshed so v0.3 and v0.4 readers
see the current API names and current guarantees;</li>
<li class="">snapshot decoding and checkpoint failure paths were hardened after
release.</li>
</ul>
<p>That last group matters almost as much as the feature list. A database
release is only useful if the error paths get clearer as the system grows.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="from-lorasnap-to-loracol1">From LORASNAP To LORACOL1<a href="https://loradb.com/blog/loradb-v0-6-columnar-checkpoints#from-lorasnap-to-loracol1" class="hash-link" aria-label="Direct link to From LORASNAP To LORACOL1" title="Direct link to From LORASNAP To LORACOL1" translate="no">​</a></h2>
<p>The original snapshot work proved the operator contract:</p>
<ul>
<li class="">write a full graph to a temporary file;</li>
<li class=""><code>fsync</code>;</li>
<li class="">rename atomically;</li>
<li class="">load by swapping the live graph in one shot;</li>
<li class="">report metadata back to the caller.</li>
</ul>
<p>That contract still stands. v0.6 changes the payload under the envelope.
<code>LORACOL1</code> is the format the current engine writes: a columnar database
snapshot with explicit metadata and checksum validation.</p>
<p>The format upgrade is part of the same philosophy as the rest of LoraDB:
keep the public boundary small, but make the internals more honest about
the work they need to do. A snapshot is not just "some bytes." It is a
database artifact that should be validated, described, restored, and used
as a recovery point.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="checkpoints-make-the-staircase-real">Checkpoints Make The Staircase Real<a href="https://loradb.com/blog/loradb-v0-6-columnar-checkpoints#checkpoints-make-the-staircase-real" class="hash-link" aria-label="Direct link to Checkpoints Make The Staircase Real" title="Direct link to Checkpoints Make The Staircase Real" translate="no">​</a></h2>
<p>The persistence staircase now reads cleanly:</p>
<ol>
<li class=""><strong>In-memory.</strong> Start fresh. No durability. Fastest loop.</li>
<li class=""><strong>Snapshot-only.</strong> Save and restore a point-in-time graph.</li>
<li class=""><strong>WAL-backed.</strong> Replay committed writes after a process restart.</li>
<li class=""><strong>Snapshot plus WAL checkpointing.</strong> Bound replay by writing
snapshots stamped with the WAL fence they represent.</li>
</ol>
<p>v0.6 strengthens the fourth step.</p>
<p>A checkpoint snapshot is a normal snapshot with a meaningful <code>walLsn</code>.
Recovery can load that graph state, then replay committed WAL records
newer than the checkpoint. That keeps the log useful without letting
replay grow forever.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="managed-commit-count-snapshots">Managed Commit-Count Snapshots<a href="https://loradb.com/blog/loradb-v0-6-columnar-checkpoints#managed-commit-count-snapshots" class="hash-link" aria-label="Direct link to Managed Commit-Count Snapshots" title="Direct link to Managed Commit-Count Snapshots" translate="no">​</a></h2>
<p>Raw-WAL helpers can now write managed snapshots after N committed
transactions.</p>
<p>That is intentionally not a full scheduler. There is still no wall-clock
checkpoint daemon hidden inside the engine. If a production process wants
time-based checkpoints, it should run that policy from the host process,
systemd, cron, an operator, or eventually the hosted platform.</p>
<p>The commit-count option covers a different need: "after this many writes,
bound recovery again." It is a small operational primitive with a clear
contract, and it gives filesystem-backed bindings a practical default path
without pretending LoraDB is already a full managed service.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="binding-alignment">Binding Alignment<a href="https://loradb.com/blog/loradb-v0-6-columnar-checkpoints#binding-alignment" class="hash-link" aria-label="Direct link to Binding Alignment" title="Direct link to Binding Alignment" translate="no">​</a></h2>
<p>v0.6 also cleaned up the API story across the surfaces:</p>
<ul>
<li class="">Rust exposes the reference checkpoint, snapshot, WAL, and sync-mode
controls.</li>
<li class=""><code>lora-server</code> keeps the operator-facing admin routes.</li>
<li class="">Node, Python, Go, and Ruby can open container-backed named databases or
explicit WAL directories.</li>
<li class="">WASM remains pathless and snapshot-oriented, using byte/source APIs
because the browser has no ordinary filesystem path.</li>
</ul>
<p>The binding names differ where the host language expects them to differ.
The mental model should not.</p>
<p>That is why the v0.3 and v0.4 posts were refreshed instead of left as
stale artifacts. They are historical release notes, but readers should not
copy an API that the current packages no longer expose.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-is-still-honest">What Is Still Honest<a href="https://loradb.com/blog/loradb-v0-6-columnar-checkpoints#what-is-still-honest" class="hash-link" aria-label="Direct link to What Is Still Honest" title="Direct link to What Is Still Honest" translate="no">​</a></h2>
<p>v0.6 does not turn LoraDB into a distributed database.</p>
<p>The boundaries remain:</p>
<ul>
<li class="">one process owns the graph;</li>
<li class="">the HTTP admin surface still needs your ingress/auth boundary;</li>
<li class="">checkpoints are local filesystem artifacts;</li>
<li class="">raw-WAL helpers can checkpoint by commit count, not wall-clock time;</li>
<li class="">there is no hosted control plane yet;</li>
<li class="">WASM snapshots are caller-managed bytes.</li>
</ul>
<p>Those limits are not a weakness in the release story. They are the reason
the story is readable.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="how-v06-fits-the-journey">How v0.6 Fits The Journey<a href="https://loradb.com/blog/loradb-v0-6-columnar-checkpoints#how-v06-fits-the-journey" class="hash-link" aria-label="Direct link to How v0.6 Fits The Journey" title="Direct link to How v0.6 Fits The Journey" translate="no">​</a></h2>
<p>v0.1 asked whether a small Rust graph database could feel good to use.
v0.2 made AI context a native value, not a bolt-on store. v0.3 made the
graph portable. v0.4 made committed writes recoverable. v0.5 made larger
query results and binding streams practical.</p>
<p>v0.6 makes the persistence layer feel less like a set of features and more
like a system.</p>
<p>That is the journey LoraDB is on: not "ship every database feature at
once," but make each boundary stronger before building the next one on top.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="read-next">Read Next<a href="https://loradb.com/blog/loradb-v0-6-columnar-checkpoints#read-next" class="hash-link" aria-label="Direct link to Read Next" title="Direct link to Read Next" translate="no">​</a></h2>
<ul>
<li class=""><a class="" href="https://loradb.com/docs/snapshot">Snapshots</a></li>
<li class=""><a class="" href="https://loradb.com/docs/wal">WAL and checkpoints</a></li>
<li class=""><a class="" href="https://loradb.com/docs/getting-started/server">HTTP server quickstart</a></li>
<li class=""><a class="" href="https://loradb.com/docs/api/http">HTTP API reference</a></li>
</ul>
<p>v0.6 is the release where snapshots stop being only a manual save file and
become the checkpoint language of the database.</p>]]></content:encoded>
            <category>Release notes</category>
            <category>Announcement</category>
            <category>Persistence</category>
            <category>Operations</category>
        </item>
        <item>
            <title><![CDATA[LoraDB v0.5: streaming queries, property indexes, and faster bindings]]></title>
            <link>https://loradb.com/blog/loradb-v0-5-streaming</link>
            <guid>https://loradb.com/blog/loradb-v0-5-streaming</guid>
            <pubDate>Mon, 27 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[LoraDB v0.5 turns the engine from whole-result execution toward pull-based streaming, property indexes, owned result streams, and binding APIs that can handle larger graph workloads without forcing everything through one materialized response.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="LoraDB v0.5 — streaming queries, property indexes, and faster bindings." src="https://loradb.com/assets/images/loradb-v0-5-streaming-header-ba0d99c1b8cfdb702701b1490161aca0.png" width="1280" height="400" class="img__Ss2"></p>
<p>LoraDB v0.5 is the release where the engine starts to breathe under
larger result sets.</p>
<p>The first public releases were about making the model real: an in-memory
graph, Cypher-shaped queries, vectors, snapshots, and then a WAL. v0.5
moves a level deeper. It changes how rows move through the executor, how
common property lookups avoid scans, and how bindings expose results
without requiring every query to become one large JSON payload.</p>
<p>The product promise is still the same: keep the graph close to the
application, make the hot path fast, and keep the system small enough to
understand. v0.5 makes that promise more practical once the graph stops
being a demo-sized toy.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-changed">What Changed<a href="https://loradb.com/blog/loradb-v0-5-streaming#what-changed" class="hash-link" aria-label="Direct link to What Changed" title="Direct link to What Changed" translate="no">​</a></h2>
<p>The short version:</p>
<ul>
<li class="">a pull-based streaming executor for query results;</li>
<li class="">transactional query streams in the database layer;</li>
<li class="">owned query streams that can cross binding boundaries safely;</li>
<li class="">client stream APIs for Node, WebAssembly, Python, and Go;</li>
<li class="">snapshot helper updates across bindings;</li>
<li class="">graph storage property indexes;</li>
<li class="">memory indexing improvements;</li>
<li class="">stronger container-backed WAL persistence;</li>
<li class="">follow-up fixes across v0.5.1 through v0.5.6 for Node handles, WAL
working directories, temporary paths, and persistence edge cases.</li>
</ul>
<p>That patch-release tail matters. v0.5 was not one neat switch. It was a
weekend of making the new execution shape work across the surfaces people
actually install.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="why-streaming-came-next">Why Streaming Came Next<a href="https://loradb.com/blog/loradb-v0-5-streaming#why-streaming-came-next" class="hash-link" aria-label="Direct link to Why Streaming Came Next" title="Direct link to Why Streaming Came Next" translate="no">​</a></h2>
<p>Before v0.5, the natural API shape was "run a query, get the result."
That is useful, simple, and exactly right for small graphs. It also has a
ceiling: every result has to be produced, stored, converted, and returned
before the caller can do anything useful with the first row.</p>
<p>Graph queries make that ceiling show up early. A traversal can discover a
large neighborhood even when the application only wants to page, filter,
or process rows incrementally. If the executor has to materialize the
whole thing at once, memory usage starts to reflect the worst moment of
the query instead of the shape the caller actually needs.</p>
<p>v0.5 starts moving the engine toward this model:</p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">MATCH (account:Account)-[:OWNS]-&gt;(ticket:Ticket)
WHERE account.id = $account_id
RETURN ticket.id, ticket.status, ticket.priority
ORDER BY ticket.priority DESC</code></pre></div>
<p>The database should be able to produce rows as the plan is pulled, and
the binding should be able to hand those rows to the host runtime without
pretending the result has to be one giant object.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-the-pull-based-executor-means">What The Pull-Based Executor Means<a href="https://loradb.com/blog/loradb-v0-5-streaming#what-the-pull-based-executor-means" class="hash-link" aria-label="Direct link to What The Pull-Based Executor Means" title="Direct link to What The Pull-Based Executor Means" translate="no">​</a></h2>
<p>The pull executor changes the flow from "plan runs to completion" to
"the consumer asks for the next row."</p>
<p>That matters for three reasons.</p>
<p>First, it makes memory behavior easier to reason about. A query can carry
the current row, the current traversal state, and the current operator
state instead of eagerly building the full output.</p>
<p>Second, it creates a better API boundary for bindings. Node, Python, Go,
and WASM all have different ideas about iteration, async work, and
backpressure. Owned streams give each runtime a safer handle to wrap.</p>
<p>Third, it is the shape future performance work wants. Once the executor
has a pull contract, improvements like paging, cancellation, and more
selective operators have a place to attach.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="property-indexes-move-the-store-closer-to-the-query">Property Indexes Move The Store Closer To The Query<a href="https://loradb.com/blog/loradb-v0-5-streaming#property-indexes-move-the-store-closer-to-the-query" class="hash-link" aria-label="Direct link to Property Indexes Move The Store Closer To The Query" title="Direct link to Property Indexes Move The Store Closer To The Query" translate="no">​</a></h2>
<p>v0.5 also adds graph storage property indexes.</p>
<p>The early LoraDB store was deliberately simple. That was the right first
move: prove the model, keep the code readable, and only add shortcuts
where the query engine has shown the need. By v0.5, a few common query
shapes were loud enough:</p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">MATCH (u:User)
WHERE u.id = $id
RETURN u</code></pre></div>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">MATCH (d:Doc)
WHERE d.slug = $slug
RETURN d.title</code></pre></div>
<p>Those should not feel like whole-graph scans. Property indexes make the
storage layer more graph-aware without changing the Cypher surface. The
developer still writes the same query; the engine gets a better access
path.</p>
<p>That is the LoraDB pattern in miniature: keep the public model steady,
then make the internals earn their place.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="binding-streams">Binding Streams<a href="https://loradb.com/blog/loradb-v0-5-streaming#binding-streams" class="hash-link" aria-label="Direct link to Binding Streams" title="Direct link to Binding Streams" translate="no">​</a></h2>
<p>The binding work is part of the release, not an afterthought.</p>
<p>LoraDB is only developer-first if the non-Rust surfaces feel first-class.
v0.5 extends the stream story through:</p>
<ul>
<li class="">Node / TypeScript query streams;</li>
<li class="">WebAssembly worker streams and snapshot helpers;</li>
<li class="">Python sync and async stream surfaces;</li>
<li class="">Go streams, transactions, and snapshot byte helpers;</li>
<li class="">Rust owned query streams in the database crate.</li>
</ul>
<p>The details differ by runtime, but the journey is the same: start with a
local graph, query it with Cypher, and process results in the shape your
application already understands.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="persistence-fixes-in-the-v05-patch-train">Persistence Fixes In The v0.5 Patch Train<a href="https://loradb.com/blog/loradb-v0-5-streaming#persistence-fixes-in-the-v05-patch-train" class="hash-link" aria-label="Direct link to Persistence Fixes In The v0.5 Patch Train" title="Direct link to Persistence Fixes In The v0.5 Patch Train" translate="no">​</a></h2>
<p>v0.4 introduced WAL-backed persistence. v0.5 made that path more useful
and then spent several patches tightening the practical edges.</p>
<p>The patch train covered:</p>
<ul>
<li class="">archive WAL persistence improvements;</li>
<li class="">Node multiple-database handle fixes;</li>
<li class="">WAL working-directory fixes;</li>
<li class="">temporary-path fixes;</li>
<li class="">durability updates for container-backed opens.</li>
</ul>
<p>That is part of the story worth saying plainly. Persistence is not a
single checkbox. It becomes trustworthy through boring fixes: paths,
handles, flushes, recovery fences, and reopening the same graph the way
real applications reopen it.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="how-v05-fits-the-journey">How v0.5 Fits The Journey<a href="https://loradb.com/blog/loradb-v0-5-streaming#how-v05-fits-the-journey" class="hash-link" aria-label="Direct link to How v0.5 Fits The Journey" title="Direct link to How v0.5 Fits The Journey" translate="no">​</a></h2>
<p>The first four releases answered "can I model, query, save, and recover
the graph?"</p>
<p>v0.5 starts answering "can I keep using it when the result set gets
large, the bindings matter, and common lookups need to stay cheap?"</p>
<p>It is a performance release, but not only in the benchmark sense. It is a
product-feel release. The graph can be closer to the application because
the application does not have to wait for every row before touching the
first one.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="read-next">Read Next<a href="https://loradb.com/blog/loradb-v0-5-streaming#read-next" class="hash-link" aria-label="Direct link to Read Next" title="Direct link to Read Next" translate="no">​</a></h2>
<ul>
<li class=""><a class="" href="https://loradb.com/docs/performance">Performance</a></li>
<li class=""><a class="" href="https://loradb.com/docs/getting-started/node">Node guide</a></li>
<li class=""><a class="" href="https://loradb.com/docs/getting-started/python">Python guide</a></li>
<li class=""><a class="" href="https://loradb.com/docs/getting-started/go">Go guide</a></li>
<li class=""><a class="" href="https://loradb.com/docs/snapshot">Snapshots</a></li>
</ul>
<p>v0.3 gave LoraDB a file. v0.4 gave it a log. v0.5 gives the query path a
stream.</p>]]></content:encoded>
            <category>Release notes</category>
            <category>Announcement</category>
            <category>Performance</category>
            <category>Cypher</category>
        </item>
        <item>
            <title><![CDATA[LoraDB v0.4.0: WAL, checkpoints, and crash recovery]]></title>
            <link>https://loradb.com/blog/loradb-v0-4-0-wal</link>
            <guid>https://loradb.com/blog/loradb-v0-4-0-wal</guid>
            <pubDate>Sat, 25 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[LoraDB v0.4.0 adds continuous durability on every filesystem-backed surface via a write-ahead log, plus checkpoints, recovery, WAL admin routes, and simple directory-based persistent startup for embedded bindings.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="LoraDB v0.4 — WAL, checkpoints, and crash recovery." src="https://loradb.com/assets/images/loradb-v0-4-0-wal-header-2a34126e9d64265624ebac83f42903fe.png" width="1280" height="400" class="img__Ss2"></p>
<p>LoraDB v0.4.0 adds a write-ahead log.</p>
<div class="theme-admonition theme-admonition-info admonition_IZjC alert alert--info"><div class="admonitionHeading_uVvU"><span class="admonitionIcon_HiR3"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>Current API note</div><div class="admonitionContent_bl22"><p>This release note has been updated with the current binding names. The
v0.4.0 release introduced WAL-backed persistence; current builds also
expose explicit raw-WAL helpers (<code>openWalDatabase</code>, <code>open_wal</code>,
<code>OpenWal</code>) and managed commit-count snapshots on the filesystem-backed
bindings.</p></div></div>
<p>The engine is still in-memory and local-first. What changes in this
release is the durability boundary: on the surfaces that own a
filesystem and process lifecycle, committed writes no longer have to
live entirely in RAM between two manual snapshots.</p>
<p>The shortest mental model:</p>
<ul>
<li class=""><code>createDatabase()</code> in Node is still a fresh in-memory graph.</li>
<li class=""><code>createDatabase("application", { databaseDir: "./data" })</code> opens a
persistent container-backed graph at <code>./data/application.loradb</code>.</li>
<li class=""><code>Database.create("app", {"database_dir": "./data"})</code>, <code>lora.New("app", lora.Options{DatabaseDir: "./data"})</code>, and
<code>LoraRuby::Database.create("app", {"database_dir": "./data"})</code> do the same thing on Python,
Go, and Ruby.</li>
<li class=""><code>openWalDatabase({ walDir: "./data/wal" })</code>,
<code>Database.open_wal("./data/wal")</code>, <code>lora.OpenWal(...)</code>, and
<code>LoraRuby::Database.open_wal("./data/wal")</code> open explicit WAL
directories; pair them with snapshot directories for managed
commit-count checkpoints.</li>
<li class=""><code>lora-server --wal-dir /var/lib/lora/wal</code> turns the HTTP server into
a WAL-backed process.</li>
<li class="">Rust gets the full open, recover, checkpoint, and sync-mode surface.</li>
</ul>
<p>Snapshots do not go away. They stay the portable file you can back up,
ship, and restore elsewhere. v0.4.0 makes them stronger by giving them
something to checkpoint against.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-ships-in-v040">What ships in v0.4.0<a href="https://loradb.com/blog/loradb-v0-4-0-wal#what-ships-in-v040" class="hash-link" aria-label="Direct link to What ships in v0.4.0" title="Direct link to What ships in v0.4.0" translate="no">​</a></h2>
<table><thead><tr><th>Surface</th><th>New durability surface</th></tr></thead><tbody><tr><td>Rust (<code>lora-database</code>)</td><td><code>Database::open_with_wal(...)</code>, <code>Database::recover(...)</code>, <code>Database::checkpoint_to(...)</code>, <code>WalConfig</code>, <code>SyncMode</code></td></tr><tr><td>Node (<code>@loradb/lora-node</code>)</td><td><code>await createDatabase(name, { databaseDir })</code> for <code>.loradb</code> archives, or <code>await openWalDatabase({ walDir, snapshotDir })</code> for explicit WAL directories with managed snapshots and sync-mode control</td></tr><tr><td>Python (<code>lora_python</code>)</td><td><code>Database.create(name, {"database_dir": dir})</code> / <code>AsyncDatabase.create(...)</code> for <code>.loradb</code> archives, or <code>Database.open_wal(wal_dir, options)</code> / <code>AsyncDatabase.open_wal(...)</code> for explicit WAL directories with managed snapshots</td></tr><tr><td>Go (<code>lora-go</code>)</td><td><code>lora.New(name, lora.Options{DatabaseDir: dir})</code> for <code>.loradb</code> archives, or <code>lora.OpenWal(lora.WalOptions{...})</code> for explicit WAL directories with managed snapshots</td></tr><tr><td>Ruby (<code>lora-ruby</code>)</td><td><code>LoraRuby::Database.create(name, { database_dir: dir })</code> for <code>.loradb</code> archives, or <code>LoraRuby::Database.open_wal(wal_dir, options)</code> for explicit WAL directories with managed snapshots</td></tr><tr><td>HTTP server (<code>lora-server</code>)</td><td><code>--wal-dir</code>, <code>--wal-sync-mode</code>, <code>--restore-from</code>, <code>POST /admin/checkpoint</code>, <code>POST /admin/wal/status</code>, <code>POST /admin/wal/truncate</code></td></tr><tr><td>Every binding</td><td>Snapshot save / load stays available exactly as before</td></tr></tbody></table>
<p>That split is intentional. Rust and <code>lora-server</code> expose the full
operator surface. Embedded bindings offer two ergonomic shapes: pass a
database name plus a directory for a portable <code>.loradb</code> archive, or use
the explicit WAL helper when you want to manage the WAL and checkpoint
directories yourself. Omit persistence options when you want a fresh
in-memory graph.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-node-shape-is-deliberately-explicit">The Node shape is deliberately explicit<a href="https://loradb.com/blog/loradb-v0-4-0-wal#the-node-shape-is-deliberately-explicit" class="hash-link" aria-label="Direct link to The Node shape is deliberately explicit" title="Direct link to The Node shape is deliberately explicit" translate="no">​</a></h2>
<p>The Node API is meant to read like the difference between scratch,
container-backed, and explicit-WAL storage:</p>
<div class="language-ts codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-ts codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token keyword">import</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> createDatabase</span><span class="token punctuation">,</span><span class="token plain"> openWalDatabase </span><span class="token punctuation">}</span><span class="token plain"> </span><span class="token keyword">from</span><span class="token plain"> </span><span class="token string">'@loradb/lora-node'</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> scratch </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> </span><span class="token function">createDatabase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain">                         </span><span class="token comment">// in-memory</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> db </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> </span><span class="token function">createDatabase</span><span class="token punctuation">(</span><span class="token string">"application"</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  databaseDir</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"./data"</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain">                                                            </span><span class="token comment">// ./data/application.loradb</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> walDb </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> </span><span class="token function">openWalDatabase</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  walDir</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"./data/application.wal"</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  snapshotDir</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"./data/application.snapshots"</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  snapshotEveryCommits</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1000</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  syncMode</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"groupSync"</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span><span class="token string">"CREATE (:Person {name: 'Ada'})"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></div></code></pre></div></div>
<p>Reopen the same directory later:</p>
<div class="language-ts codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-ts codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token keyword">import</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> createDatabase </span><span class="token punctuation">}</span><span class="token plain"> </span><span class="token keyword">from</span><span class="token plain"> </span><span class="token string">'@loradb/lora-node'</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> db </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> </span><span class="token function">createDatabase</span><span class="token punctuation">(</span><span class="token string">"application"</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> databaseDir</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"./data"</span><span class="token plain"> </span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> rows </span><span class="token punctuation">}</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token string">"MATCH (p:Person) RETURN p.name AS name"</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></div></code></pre></div></div>
<p>The name is resolved inside <code>databaseDir</code> as a <code>.loradb</code> archive. Relative
paths resolve from the current working directory. Archive-backed named
databases default to grouped fsync with a 1s interval. Raw WAL helpers
default to:</p>
<ul>
<li class=""><code>SyncMode::GroupSync</code></li>
<li class=""><code>8 MiB</code> target segment size</li>
</ul>
<p>Node exposes <code>syncMode</code> for container-backed and explicit WAL opens. It
does <strong>not</strong> expose WAL status or truncate helpers yet. If you need
that operator surface today, use Rust directly or run <code>lora-server</code>.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-the-wal-actually-guarantees">What the WAL actually guarantees<a href="https://loradb.com/blog/loradb-v0-4-0-wal#what-the-wal-actually-guarantees" class="hash-link" aria-label="Direct link to What the WAL actually guarantees" title="Direct link to What the WAL actually guarantees" translate="no">​</a></h2>
<p>This is not "marketing durability." The contract is concrete:</p>
<ul>
<li class="">Every mutating query is logged before the call returns.</li>
<li class="">Read-only queries write nothing to the WAL.</li>
<li class="">Recovery replays only committed writes, in commit order.</li>
<li class="">A torn tail on the active segment is truncated back to the last
valid record.</li>
<li class="">A checkpoint writes a snapshot stamped with <code>walLsn</code>, appends a
checkpoint marker, and truncates safe WAL history up to that fence.</li>
</ul>
<p>That means the engine now has a clean recovery staircase:</p>
<ol>
<li class="">pure in-memory,</li>
<li class="">snapshot-only,</li>
<li class="">WAL-only,</li>
<li class="">snapshot + WAL checkpointing.</li>
</ol>
<p>The important part is that each step is readable. The release does not
blur "we can save a file" together with "we can recover committed
writes." Those are different guarantees, and the docs now say so.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="lora-server-grows-into-an-operator-surface"><code>lora-server</code> grows into an operator surface<a href="https://loradb.com/blog/loradb-v0-4-0-wal#lora-server-grows-into-an-operator-surface" class="hash-link" aria-label="Direct link to lora-server-grows-into-an-operator-surface" title="Direct link to lora-server-grows-into-an-operator-surface" translate="no">​</a></h2>
<p>The HTTP server picks up the real production-adjacent durability knobs:</p>
<div class="language-bash codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-bash codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token plain"># Fresh boot with a WAL.</span><br></div><div class="token-line"><span class="token plain">lora-server --wal-dir /var/lib/lora/wal</span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"># Snapshot + WAL recovery.</span><br></div><div class="token-line"><span class="token plain">lora-server \</span><br></div><div class="token-line"><span class="token plain">  --wal-dir /var/lib/lora/wal \</span><br></div><div class="token-line"><span class="token plain">  --snapshot-path /var/lib/lora/graph.bin \</span><br></div><div class="token-line"><span class="token plain">  --restore-from /var/lib/lora/graph.bin</span><br></div></code></pre></div></div>
<p>And the admin routes that make the log inspectable and manageable:</p>
<ul>
<li class=""><code>POST /admin/wal/status</code></li>
<li class=""><code>POST /admin/wal/truncate</code></li>
<li class=""><code>POST /admin/checkpoint</code></li>
</ul>
<p>WAL durability now uses GroupSync:</p>
<table><thead><tr><th>Mode</th><th>Meaning</th></tr></thead><tbody><tr><td><code>group-sync</code></td><td>write commits immediately and fsync in the background or on explicit sync/checkpoint/drop</td></tr></tbody></table>
<p>The server is still honest about its boundary: one process, one graph,
no auth, no TLS, no multi-database routing. The durability story is
stronger, but it is still a small local system, not a hosted graph
service in disguise.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-did-not-change">What did not change<a href="https://loradb.com/blog/loradb-v0-4-0-wal#what-did-not-change" class="hash-link" aria-label="Direct link to What did not change" title="Direct link to What did not change" translate="no">​</a></h2>
<p>v0.4.0 is a durability release, not a reinvention of the product:</p>
<ul>
<li class="">LoraDB is still an in-memory engine.</li>
<li class="">Snapshots are still manual and explicit.</li>
<li class="">There is still no wall-clock checkpoint scheduler. Raw-WAL helpers
can write managed snapshots after N committed transactions.</li>
<li class="">Full checkpoint, truncate, and status controls still live on Rust and
<code>lora-server</code>; Node exposes sync-mode control, while Python, Go, and
Ruby keep the raw-WAL helper intentionally small.</li>
<li class="">WASM stays snapshot-only and pathless, using <code>saveSnapshot</code> /
<code>loadSnapshot</code>.</li>
<li class="">The HTTP admin surface is still unauthenticated and meant to live
behind your own ingress.</li>
</ul>
<p>That last point matters. The new admin routes are useful, but only when
deployed behind a boundary you control.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-docs-changed-with-the-product">The docs changed with the product<a href="https://loradb.com/blog/loradb-v0-4-0-wal#the-docs-changed-with-the-product" class="hash-link" aria-label="Direct link to The docs changed with the product" title="Direct link to The docs changed with the product" translate="no">​</a></h2>
<p>This release also forced a documentation cleanup across the website.
The main fixes:</p>
<ul>
<li class="">the embedded-binding docs now state the initialization rule
explicitly: no argument means in-memory, name plus directory means
<code>.loradb</code> container-backed, and explicit WAL helpers own raw WAL
directories;</li>
<li class="">the HTTP server and API docs no longer describe a pre-WAL world;</li>
<li class="">snapshots are now documented as a standalone primitive that can also
act as a checkpoint target;</li>
<li class="">there is a dedicated WAL page instead of scattering durability
details across unrelated guides.</li>
</ul>
<p>The result is that the website now answers the operational questions in
the same place the release introduces them.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="read-next">Read next<a href="https://loradb.com/blog/loradb-v0-4-0-wal#read-next" class="hash-link" aria-label="Direct link to Read next" title="Direct link to Read next" translate="no">​</a></h2>
<ul>
<li class=""><a class="" href="https://loradb.com/docs/wal">WAL and checkpoints</a></li>
<li class=""><a class="" href="https://loradb.com/docs/snapshot">Snapshots</a></li>
<li class=""><a class="" href="https://loradb.com/docs/getting-started/node">Node guide</a></li>
<li class=""><a class="" href="https://loradb.com/docs/getting-started/server">HTTP server quickstart</a></li>
<li class=""><a class="" href="https://loradb.com/docs/api/http">HTTP API reference</a></li>
</ul>
<p>v0.3 made "save the graph to one file" real. v0.4.0 makes "reopen the
process and keep committed writes" real. That is the release.</p>]]></content:encoded>
            <category>Release notes</category>
            <category>Announcement</category>
            <category>Persistence</category>
            <category>Operations</category>
        </item>
        <item>
            <title><![CDATA[Snapshots before a log]]></title>
            <link>https://loradb.com/blog/snapshots-before-a-log</link>
            <guid>https://loradb.com/blog/snapshots-before-a-log</guid>
            <pubDate>Sat, 25 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Why LoraDB v0.3 ships manual point-in-time snapshots before any write-ahead log, what that primitive teaches, and how durability gets layered on top of it.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Snapshots before a log — the simpler primitive earns the harder one." src="https://loradb.com/assets/images/snapshots-before-a-log-header-b5588d0c715904e7fe1f7e88d597394a.png" width="1280" height="400" class="img__Ss2"></p>
<p>Most databases I have worked with had a write-ahead log before they had a
snapshot story. LoraDB went the other way.</p>
<div class="theme-admonition theme-admonition-info admonition_IZjC alert alert--info"><div class="admonitionHeading_uVvU"><span class="admonitionIcon_HiR3"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>Current API note</div><div class="admonitionContent_bl22"><p>This founder note is historical. The WAL has since landed on every
filesystem-backed surface, snapshots now write the columnar <code>LORACOL1</code>
format with compression/encryption support, and WASM uses
<code>saveSnapshot</code> / <code>loadSnapshot</code> instead of the older byte-specific
method names.</p></div></div>
<p>At the time, v0.3 shipped manual point-in-time snapshots and nothing else on the
persistence side. No append-only log. No background checkpoint loop. No
continuous durability. One file on disk, taken on demand, atomic on
rename.</p>
<p>The order is intentional, and it is the kind of decision that is easier
to defend before the release than after.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-default-narrative-for-adding-persistence">The Default Narrative For "Adding Persistence"<a href="https://loradb.com/blog/snapshots-before-a-log#the-default-narrative-for-adding-persistence" class="hash-link" aria-label="Direct link to The Default Narrative For &quot;Adding Persistence&quot;" title="Direct link to The Default Narrative For &quot;Adding Persistence&quot;" translate="no">​</a></h2>
<p>The default narrative for adding persistence to an in-memory database is
some version of:</p>
<ol>
<li class="">ship a write-ahead log,</li>
<li class="">background checkpoints flush state to disk,</li>
<li class="">on boot, replay the log on top of the latest checkpoint,</li>
<li class="">announce "durable."</li>
</ol>
<p>That is what mature databases do, eventually. It is also the wrong
<em>first</em> step for a project where the data model and the storage tier are
both still settling.</p>
<p>A WAL is a long-term commitment to a concrete write path. Every mutation
has to know how to serialize itself. Every recovery routine has to
dispatch on event type. Every release after the first one inherits the
log format, the recovery state machine, and the assumptions baked into
both. Get any of that wrong on day one and the project carries the
mistake forward — or pays for an expensive migration to fix it.</p>
<p>For a database that is two minor versions old and still figuring out
what its read and write paths look like, that is too much surface area
to commit to.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-a-snapshot-teaches-that-a-wal-would-not">What A Snapshot Teaches That A WAL Would Not<a href="https://loradb.com/blog/snapshots-before-a-log#what-a-snapshot-teaches-that-a-wal-would-not" class="hash-link" aria-label="Direct link to What A Snapshot Teaches That A WAL Would Not" title="Direct link to What A Snapshot Teaches That A WAL Would Not" translate="no">​</a></h2>
<p>A snapshot is the lowest-risk way to learn the shape of "the graph as a
serialized artifact." It forces the project to answer a small set of
concrete questions:</p>
<ul>
<li class="">What does the file format look like? v0.3 answered with the original
<code>LORASNAP</code> envelope; current releases write <code>LORACOL1</code>, a columnar
snapshot payload with a BLAKE3 checksum plus compression and
encryption metadata.</li>
<li class="">How is the write atomic? <code>&lt;path&gt;.tmp</code>, <code>fsync</code>, rename over the
target.</li>
<li class="">How is the read atomic? Hold the store mutex, validate, swap the
graph in one shot.</li>
<li class="">What does the API look like across every binding? <code>save_snapshot</code>,
<code>load_snapshot</code>, <code>in_memory_from_snapshot</code>, plus an opt-in HTTP admin
surface.</li>
<li class="">What does every binding return? A single <code>SnapshotMeta</code> shape with
<code>formatVersion</code>, <code>nodeCount</code>, <code>relationshipCount</code>, and a reserved
<code>walLsn</code>.</li>
<li class="">What does the operator contract look like? <code>--snapshot-path</code>,
<code>--restore-from</code>, off-by-default admin endpoints, no auth, behind
ingress only.</li>
</ul>
<p>None of those answers went away when the WAL arrived. A checkpoint, by
definition, is a snapshot with a WAL LSN attached. In current releases,
pure snapshots report <code>walLsn: null</code>; checkpoint snapshots stamp it
with the durable WAL fence, and recovery replays committed records
newer than that fence.</p>
<p>In other words, the snapshot work was not throwaway scaffolding. It is
the foundation the current log now sits on.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-a-snapshot-is-honest-about">What A Snapshot Is Honest About<a href="https://loradb.com/blog/snapshots-before-a-log#what-a-snapshot-is-honest-about" class="hash-link" aria-label="Direct link to What A Snapshot Is Honest About" title="Direct link to What A Snapshot Is Honest About" translate="no">​</a></h2>
<p>A snapshot is not durability. It is point-in-time persistence. The
difference matters.</p>
<ul>
<li class="">A crash between two saves loses every mutation in the window.</li>
<li class="">The store mutex is held for the duration of both save and load.
Concurrent queries block.</li>
<li class="">There is no incremental save. The whole graph serializes each time.</li>
<li class="">There is no auto-cadence. Saves happen because someone called
<code>save_snapshot</code> or hit the admin endpoint.</li>
</ul>
<p>That set of caveats is also exactly what makes the primitive useful
right now without overcommitting. A single-node service, a notebook, a
seeded process, a service with a controlled shutdown window, a backup
cron — all of those need the property "the graph as of now, written to
one file." None of them need a continuous log to be useful.</p>
<p>The shapes that genuinely need a WAL — multi-node clusters, zero-data-loss
writes, mid-second crash recovery — are not the shapes LoraDB is good at
today. Building a half-finished log inside a single-process engine ends
up with a journal that is less reliable than just snapshotting more
often, and worse, with a contract that is harder to read.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="why-honest-boundaries-matter-more-than-marketing">Why Honest Boundaries Matter More Than Marketing<a href="https://loradb.com/blog/snapshots-before-a-log#why-honest-boundaries-matter-more-than-marketing" class="hash-link" aria-label="Direct link to Why Honest Boundaries Matter More Than Marketing" title="Direct link to Why Honest Boundaries Matter More Than Marketing" translate="no">​</a></h2>
<p>The thing I see most often in databases that overpromised on durability
is silent data loss between two undocumented seams. That happens when
"persistent" is sold as a complete story before the moving parts have
settled — when there is durability marketing language without a clean
operator contract underneath.</p>
<p>The contract I want for snapshots is small enough to fit on a card:</p>
<ul>
<li class="">The save renames <code>&lt;path&gt;.tmp</code> over <code>&lt;path&gt;</code>. A crash mid-save can leave
the <code>.tmp</code> file behind. It cannot leave a half-written <code>&lt;path&gt;</code>.</li>
<li class="">The load swaps the live graph in one shot. Concurrent queries see the
old graph or the new one. Never both. Never a partial.</li>
<li class="">A crash between two saves loses every mutation in the window. Pick a
cadence accordingly.</li>
<li class="">The HTTP admin endpoints are off by default. They have no
authentication. They are intended to sit behind your ingress.</li>
</ul>
<p>That is what v0.3 ships. It is the smallest set of guarantees that
actually mean what they say. None of them are advertised more
aggressively than they are documented.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="where-the-persistence-story-goes-next">Where The Persistence Story Goes Next<a href="https://loradb.com/blog/snapshots-before-a-log#where-the-persistence-story-goes-next" class="hash-link" aria-label="Direct link to Where The Persistence Story Goes Next" title="Direct link to Where The Persistence Story Goes Next" translate="no">​</a></h2>
<p>Three steps line up against the boundary above, in order.</p>
<p><strong>A write-ahead log.</strong> This has since landed. Rust and <code>lora-server</code>
expose the full WAL/checkpoint/status/truncate surface; Node, Python,
Go, and Ruby expose container-backed opens plus explicit raw-WAL helpers.
WAL checkpoints are snapshots with a meaningful <code>walLsn</code>, exactly the
shape the snapshot contract prepared for.</p>
<p><strong>Checkpoint automation.</strong> Current raw-WAL helpers can write managed
snapshots after N committed transactions. The remaining boundary is
wall-clock scheduling: if you want time-based checkpoints, run them
from the host process, cron, systemd, or an operator loop.</p>
<p><strong>Auth on the admin surface.</strong> Token-based auth in front of <code>/admin/*</code>
so the endpoints can be enabled on hosts that face a real network
without an external reverse proxy. Hosted operations come after that,
not before — the moment to charge people to run LoraDB for them is the
moment its operator contract is durable enough to charge for.</p>
<p>There is a fourth thread that is less visible but matters more: the
contract should stay easy to read while it grows. Each step adds
capability without adding ambiguity. A WAL should not turn "durable"
into a fuzzy word; it should turn the existing snapshot contract into
a strictly stronger one.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="how-this-fits-the-customer-journey">How This Fits The Customer Journey<a href="https://loradb.com/blog/snapshots-before-a-log#how-this-fits-the-customer-journey" class="hash-link" aria-label="Direct link to How This Fits The Customer Journey" title="Direct link to How This Fits The Customer Journey" translate="no">​</a></h2>
<p>The persistence staircase mirrors the adoption staircase.</p>
<ol>
<li class=""><strong>Discovery.</strong> A developer runs <code>cargo run --bin lora-server</code> and
types a query. There is no persistence to think about yet.</li>
<li class=""><strong>Local prototype.</strong> They want to keep the graph between sessions.
<code>--snapshot-path</code> and <code>--restore-from</code> are enough.</li>
<li class=""><strong>Internal service.</strong> They want graceful-shutdown saves and
scheduled backups. <code>POST /admin/snapshot/save</code> from a systemd unit
or a cron is enough.</li>
<li class=""><strong>Production with tighter SLAs.</strong> They need continuous durability —
point-in-time recovery to the last committed WAL transaction, not
the last manual save. That is where WAL-backed opens and
checkpoints now fit.</li>
<li class=""><strong>Managed operations.</strong> They do not want to operate the engine at
all. That is when the hosted platform takes over the snapshot
cadence, the WAL config, and the auth boundary.</li>
</ol>
<p>Each step adds capability the previous step's users do not regress on.
That is the point of building from a snapshot up rather than a WAL
down.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-feedback-that-will-shape-v04">The Feedback That Will Shape v0.4<a href="https://loradb.com/blog/snapshots-before-a-log#the-feedback-that-will-shape-v04" class="hash-link" aria-label="Direct link to The Feedback That Will Shape v0.4" title="Direct link to The Feedback That Will Shape v0.4" translate="no">​</a></h2>
<p>The clearest signal for whether v0.3 lands is concrete:</p>
<ul>
<li class="">how big does your graph get, and how long does <code>save_snapshot</code> take
at that size;</li>
<li class="">what cadence did you settle on — seconds, minutes, on-shutdown only,
every-N-mutations;</li>
<li class="">did atomic rename land cleanly on your filesystem (we test on Linux
ext4/xfs and macOS APFS);</li>
<li class="">which binding did you use, and did the WASM pathless
<code>saveSnapshot</code> / <code>loadSnapshot</code> surface fit your storage layer
(IndexedDB, OPFS, a backend POST) without extra glue;</li>
<li class="">what does your ingress look like for the admin endpoints — reverse
proxy, Unix socket, or "not exposed at all";</li>
<li class="">where did manual snapshots stop being acceptable for your workload,
and what WAL sync/checkpoint cadence did you need?</li>
</ul>
<p>The answers decide what cadence the WAL has to support, which crash
windows we need to harden first, and which surfaces need the auth
contract before the rest.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="closing">Closing<a href="https://loradb.com/blog/snapshots-before-a-log#closing" class="hash-link" aria-label="Direct link to Closing" title="Direct link to Closing" translate="no">​</a></h2>
<p>LoraDB's persistence story is not "we shipped a quarter of a WAL." It
is "we shipped the smallest persistence primitive that means what it
says, and the next steps build on top of it without changing what we
already promised."</p>
<p>Snapshots before a log is the order I would pick if I had to do this
again. The right first step toward durability is not a journal. It is
a contract.</p>
<hr>
<p>Canonical references:</p>
<ul>
<li class=""><a class="" href="https://loradb.com/docs/snapshot">Snapshots</a> — file format, atomic-rename
protocol, binding examples, and the security warning on the admin
surface.</li>
<li class=""><a class="" href="https://loradb.com/docs/getting-started/server#snapshots-wal-and-restore">HTTP server quickstart → Snapshots, WAL, and restore</a>
— <code>--snapshot-path</code> and <code>--restore-from</code> in context.</li>
<li class=""><a class="" href="https://loradb.com/blog/loradb-v0-3-snapshots">v0.3 release notes</a> — the team-side
announcement, the full binding table, and the explicit list of what
is still out of scope.</li>
</ul>]]></content:encoded>
            <category>Founder notes</category>
            <category>Persistence</category>
            <category>Design</category>
            <category>Operations</category>
        </item>
        <item>
            <title><![CDATA[LoraDB v0.3: snapshots for saving and restoring graph state]]></title>
            <link>https://loradb.com/blog/loradb-v0-3-snapshots</link>
            <guid>https://loradb.com/blog/loradb-v0-3-snapshots</guid>
            <pubDate>Fri, 24 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[LoraDB v0.3 adds manual point-in-time snapshots — a single-file dump of the in-memory graph, atomic on rename, restorable at boot or on demand, exposed through every binding and the HTTP admin surface.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="LoraDB v0.3 — manual point-in-time snapshots, atomic on rename, admin surface secured." src="https://loradb.com/assets/images/loradb-v0-3-snapshots-header-063022524ccb4505c646edf627b38346.png" width="1280" height="400" class="img__Ss2"></p>
<p>LoraDB v0.3 adds manual point-in-time snapshots.</p>
<div class="theme-admonition theme-admonition-info admonition_IZjC alert alert--info"><div class="admonitionHeading_uVvU"><span class="admonitionIcon_HiR3"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>Current API note</div><div class="admonitionContent_bl22"><p>This release note is historical, but the snippets below have been
updated to the current snapshot API. Current releases write the
columnar <code>LORACOL1</code> database snapshot format, support compression and
encryption options, expose WASM snapshots through <code>saveSnapshot</code> /
<code>loadSnapshot</code>, and can pair snapshots with WAL-backed checkpoints.</p></div></div>
<p>You can now dump the entire in-memory graph to a single file and
restore it later. The save is atomic on rename, the load replaces the
live graph in one shot, and the feature is exposed on every surface
that the engine talks through — the Rust core, the Python, Node, WASM,
Go, and Ruby bindings, the shared C FFI, and the HTTP server as an
opt-in admin endpoint.</p>
<p>What this release is <strong>not</strong> is full persistence. There is no
write-ahead log, no background checkpoint loop, no continuous
durability. A snapshot is exactly what the name says: a point-in-time
dump you take on demand. Data mutated between two saves is lost on
crash. That boundary is deliberate — making the explicit, operator-
controlled shape work cleanly is the foundation a WAL will sit on, and
it closes the "no persistence at all" gap for the workloads that only
need occasional checkpoints today (seeded services, notebooks,
controlled shutdowns, scheduled backups).</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-changed">What Changed<a href="https://loradb.com/blog/loradb-v0-3-snapshots#what-changed" class="hash-link" aria-label="Direct link to What Changed" title="Direct link to What Changed" translate="no">​</a></h2>
<p>The short list:</p>
<ul>
<li class="">A new single-file snapshot format. v0.3 introduced the original
<code>LORASNAP</code> format; current releases write the columnar <code>LORACOL1</code>
format with a BLAKE3 envelope checksum, compression metadata, and
optional encryption metadata.</li>
<li class="">Atomic saves — writes go to <code>&lt;path&gt;.tmp</code>, are <code>fsync</code>'d, and then
renamed over the target. A crashed save never leaves a half-written
file at the target path.</li>
<li class="">Atomic loads — the store mutex is held for the full restore, so
concurrent queries see the old or the new graph, never a partial
one.</li>
<li class="">A <code>walLsn</code> metadata slot for WAL/checkpoint recovery. Pure snapshots
emit it as <code>null</code>; checkpoint snapshots written by WAL-backed
surfaces stamp it with a durable fence.</li>
<li class="">Forward-compatible reader — formats are dispatched by version, so
today's v1 files will keep loading after the next format bump until
support is deliberately dropped.</li>
<li class="">Snapshot metadata (<code>formatVersion</code>, <code>nodeCount</code>,
<code>relationshipCount</code>, <code>walLsn</code>) returned from path-based saves, load
calls, and HTTP admin calls. Byte-output save helpers return bytes.</li>
</ul>
<p>Binding support, using today's API names:</p>
<table><thead><tr><th>Surface</th><th>Save</th><th>Load</th><th>Shape</th></tr></thead><tbody><tr><td>Rust (<code>lora-database</code>)</td><td><code>save_snapshot_to(path)</code></td><td><code>load_snapshot_from(path)</code>, <code>in_memory_from_snapshot(path)</code></td><td>file path</td></tr><tr><td>Python (sync <code>Database</code>)</td><td><code>save_snapshot(path)</code></td><td><code>load_snapshot(path)</code></td><td>file path</td></tr><tr><td>Python (<code>AsyncDatabase</code>)</td><td><code>await save_snapshot(path)</code></td><td><code>await load_snapshot(path)</code></td><td>file path</td></tr><tr><td>Node.js (<code>@loradb/lora-node</code>)</td><td><code>await saveSnapshot(path)</code></td><td><code>await loadSnapshot(path)</code></td><td>file path</td></tr><tr><td>WebAssembly (<code>@loradb/lora-wasm</code>)</td><td><code>await saveSnapshot()</code></td><td><code>await loadSnapshot(source)</code></td><td><code>Uint8Array</code>, <code>ArrayBuffer</code>, <code>Blob</code>, <code>Response</code>, <code>URL</code>, stream</td></tr><tr><td>Go (<code>lora-go</code>)</td><td><code>db.SaveSnapshot(path)</code></td><td><code>db.LoadSnapshot(path)</code></td><td>file path</td></tr><tr><td>Ruby (<code>lora-ruby</code>)</td><td><code>db.save_snapshot(path)</code></td><td><code>db.load_snapshot(path)</code></td><td>file path</td></tr><tr><td>C FFI (<code>lora-ffi</code>)</td><td><code>lora_db_save_snapshot(handle, path, ...)</code></td><td><code>lora_db_load_snapshot(handle, path, ...)</code></td><td>file path</td></tr><tr><td>HTTP server (<code>lora-server</code>)</td><td><code>POST /admin/snapshot/save</code></td><td><code>POST /admin/snapshot/load</code></td><td>file path on the server's disk</td></tr></tbody></table>
<p>WebAssembly is source/byte-oriented by design — WASM has no filesystem
path API, so the caller is responsible for persisting the <code>Uint8Array</code>
or web-native wrapper to IndexedDB, OPFS, a backend upload, or wherever
their app already stores state.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="why-snapshots-matter">Why Snapshots Matter<a href="https://loradb.com/blog/loradb-v0-3-snapshots#why-snapshots-matter" class="hash-link" aria-label="Direct link to Why Snapshots Matter" title="Direct link to Why Snapshots Matter" translate="no">​</a></h2>
<p>The v0.1 and v0.2 model was "one process, one in-memory graph, lost on
exit." That is fine for notebooks, tests, demos, and embedded
read-mostly caches, but it forces every operator into one of two
patterns neither of which the engine supported well:</p>
<ul>
<li class=""><strong>Reload from source on every boot.</strong> Works if the source is cheap,
but adds real seeding time on restart and pushes reload logic into
every deployment.</li>
<li class=""><strong>Rebuild a parallel persistence layer.</strong> The application writes
every mutation to an external store, then replays it on boot. A
second data model to maintain, a second consistency story.</li>
</ul>
<p>Neither is what you want for the shape of workload LoraDB is actually
good at: a graph view over data the host process already owns, or a
small seeded context that the agent / service accumulates in memory.
For those, the right primitive is a file on disk that captures "the
graph as of this moment" — cheap to take, cheap to restore, no second
data model.</p>
<p>That is what v0.3 ships. The Cypher surface does not change; the
storage tier gets one save verb (<code>save_snapshot</code>), one load verb
(<code>load_snapshot</code>), and one new file on disk.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-a-snapshot-is-not">What A Snapshot Is Not<a href="https://loradb.com/blog/loradb-v0-3-snapshots#what-a-snapshot-is-not" class="hash-link" aria-label="Direct link to What A Snapshot Is Not" title="Direct link to What A Snapshot Is Not" translate="no">​</a></h2>
<p>Same list as above, stated as the bright line:</p>
<ul>
<li class=""><strong>Not continuous durability by itself.</strong> A crash between two saves
loses every mutation in the window. Current releases can pair
snapshots with WAL-backed recovery when you need committed writes to
survive crashes.</li>
<li class=""><strong>Not a wall-clock checkpoint scheduler.</strong> Manual saves happen
because the host process, an external cron, or the admin HTTP
endpoint calls them. Current raw-WAL helpers can also write managed
snapshots after N committed transactions.</li>
<li class=""><strong>Not a general persistent storage tier.</strong> There is no storage
backend other than the in-memory graph; the snapshot is a dump of
that graph, not a format a different engine writes into.</li>
<li class=""><strong>Not zero-cost at save time.</strong> The store mutex is held for the
duration of the save. Concurrent queries wait. Pick a snapshot
cadence that leaves headroom.</li>
<li class=""><strong>Not a boundary for multi-tenancy.</strong> One process still holds one
graph; each process needs its own snapshot path.</li>
</ul>
<p>Those are not roadmap omissions hidden behind marketing language. They
are what "simple, explicit, operator-controlled" means.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="using-snapshots">Using Snapshots<a href="https://loradb.com/blog/loradb-v0-3-snapshots#using-snapshots" class="hash-link" aria-label="Direct link to Using Snapshots" title="Direct link to Using Snapshots" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="save-and-load-from-rust">Save and load from Rust<a href="https://loradb.com/blog/loradb-v0-3-snapshots#save-and-load-from-rust" class="hash-link" aria-label="Direct link to Save and load from Rust" title="Direct link to Save and load from Rust" translate="no">​</a></h3>
<p>The reference surface. Every other binding wraps these two methods.</p>
<div class="language-rust codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-rust codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token keyword">use</span><span class="token plain"> </span><span class="token namespace">lora_database</span><span class="token namespace punctuation">::</span><span class="token class-name">Database</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">let</span><span class="token plain"> db </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">Database</span><span class="token punctuation">::</span><span class="token function">in_memory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">db</span><span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span><span class="token string">"CREATE (:Person {name: 'Ada'})"</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token class-name">None</span><span class="token punctuation">)</span><span class="token operator">?</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token comment">// Dump the full graph to disk.</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">let</span><span class="token plain"> meta </span><span class="token operator">=</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">save_snapshot_to</span><span class="token punctuation">(</span><span class="token string">"graph.bin"</span><span class="token punctuation">)</span><span class="token operator">?</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token macro property">println!</span><span class="token punctuation">(</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token string">"{} nodes, {} relationships"</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    meta</span><span class="token punctuation">.</span><span class="token plain">node_count</span><span class="token punctuation">,</span><span class="token plain"> meta</span><span class="token punctuation">.</span><span class="token plain">relationship_count</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token comment">// Boot a fresh Database directly from the file.</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">let</span><span class="token plain"> db2 </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">Database</span><span class="token punctuation">::</span><span class="token function">in_memory_from_snapshot</span><span class="token punctuation">(</span><span class="token string">"graph.bin"</span><span class="token punctuation">)</span><span class="token operator">?</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token comment">// Or restore onto an existing handle (concurrent queries block on the</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token comment">// store mutex for the duration of the load).</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">db</span><span class="token punctuation">.</span><span class="token function">load_snapshot_from</span><span class="token punctuation">(</span><span class="token string">"graph.bin"</span><span class="token punctuation">)</span><span class="token operator">?</span><span class="token punctuation">;</span><br></div></code></pre></div></div>
<p>Every save and load returns a <code>SnapshotMeta</code>:</p>
<div class="language-json codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-json codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token punctuation">{</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token property">"formatVersion"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token property">"nodeCount"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1024</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token property">"relationshipCount"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">4096</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token property">"walLsn"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token null keyword">null</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token punctuation">}</span><br></div></code></pre></div></div>
<p>The <code>walLsn</code> field is <code>null</code> for pure snapshots and non-null for
checkpoint snapshots written by WAL-backed surfaces.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="save-and-load-from-python">Save and load from Python<a href="https://loradb.com/blog/loradb-v0-3-snapshots#save-and-load-from-python" class="hash-link" aria-label="Direct link to Save and load from Python" title="Direct link to Save and load from Python" translate="no">​</a></h3>
<div class="language-python codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-python codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token keyword">from</span><span class="token plain"> lora_python </span><span class="token keyword">import</span><span class="token plain"> Database</span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain">db </span><span class="token operator">=</span><span class="token plain"> Database</span><span class="token punctuation">.</span><span class="token plain">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">db</span><span class="token punctuation">.</span><span class="token plain">execute</span><span class="token punctuation">(</span><span class="token string">"CREATE (:Person {name: 'Ada'})"</span><span class="token punctuation">)</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain">meta </span><span class="token operator">=</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token plain">save_snapshot</span><span class="token punctuation">(</span><span class="token string">"graph.bin"</span><span class="token punctuation">)</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token plain">meta</span><span class="token punctuation">[</span><span class="token string">"nodeCount"</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token plain"> meta</span><span class="token punctuation">[</span><span class="token string">"relationshipCount"</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain">db2 </span><span class="token operator">=</span><span class="token plain"> Database</span><span class="token punctuation">.</span><span class="token plain">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">db2</span><span class="token punctuation">.</span><span class="token plain">load_snapshot</span><span class="token punctuation">(</span><span class="token string">"graph.bin"</span><span class="token punctuation">)</span><br></div></code></pre></div></div>
<p>The <code>AsyncDatabase</code> wrapper exposes the same two methods as
coroutines:</p>
<div class="language-python codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-python codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token keyword">import</span><span class="token plain"> asyncio</span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">from</span><span class="token plain"> lora_python </span><span class="token keyword">import</span><span class="token plain"> AsyncDatabase</span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">async</span><span class="token plain"> </span><span class="token keyword">def</span><span class="token plain"> </span><span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    db </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> AsyncDatabase</span><span class="token punctuation">.</span><span class="token plain">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token plain">execute</span><span class="token punctuation">(</span><span class="token string">"CREATE (:Person {name: 'Ada'})"</span><span class="token punctuation">)</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token plain">save_snapshot</span><span class="token punctuation">(</span><span class="token string">"graph.bin"</span><span class="token punctuation">)</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain">asyncio</span><span class="token punctuation">.</span><span class="token plain">run</span><span class="token punctuation">(</span><span class="token plain">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br></div></code></pre></div></div>
<p>Both forms run with the GIL released / on a worker thread so the event
loop stays free during large saves.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="save-and-load-from-node--typescript">Save and load from Node / TypeScript<a href="https://loradb.com/blog/loradb-v0-3-snapshots#save-and-load-from-node--typescript" class="hash-link" aria-label="Direct link to Save and load from Node / TypeScript" title="Direct link to Save and load from Node / TypeScript" translate="no">​</a></h3>
<div class="language-ts codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-ts codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token keyword">import</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> createDatabase </span><span class="token punctuation">}</span><span class="token plain"> </span><span class="token keyword">from</span><span class="token plain"> </span><span class="token string">'@loradb/lora-node'</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> db </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> </span><span class="token function">createDatabase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span><span class="token string">"CREATE (:Person {name: 'Ada'})"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> meta </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">saveSnapshot</span><span class="token punctuation">(</span><span class="token string">'graph.bin'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token plain">meta</span><span class="token punctuation">.</span><span class="token plain">nodeCount</span><span class="token punctuation">,</span><span class="token plain"> meta</span><span class="token punctuation">.</span><span class="token plain">relationshipCount</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> db2 </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> </span><span class="token function">createDatabase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">await</span><span class="token plain"> db2</span><span class="token punctuation">.</span><span class="token function">loadSnapshot</span><span class="token punctuation">(</span><span class="token string">'graph.bin'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></div></code></pre></div></div>
<p><code>saveSnapshot</code> / <code>loadSnapshot</code> return Promises that resolve to a
<code>SnapshotMeta</code> object with the same <code>formatVersion</code> / <code>nodeCount</code> /
<code>relationshipCount</code> / <code>walLsn</code> fields as every other binding.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="save-and-load-from-webassembly">Save and load from WebAssembly<a href="https://loradb.com/blog/loradb-v0-3-snapshots#save-and-load-from-webassembly" class="hash-link" aria-label="Direct link to Save and load from WebAssembly" title="Direct link to Save and load from WebAssembly" translate="no">​</a></h3>
<p>WASM has no filesystem path API, so the snapshot API is source-in /
byte-out:</p>
<div class="language-ts codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-ts codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token keyword">import</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> createDatabase </span><span class="token punctuation">}</span><span class="token plain"> </span><span class="token keyword">from</span><span class="token plain"> </span><span class="token string">'@loradb/lora-wasm'</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> db </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> </span><span class="token function">createDatabase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span><span class="token string">"CREATE (:Person {name: 'Ada'})"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token comment">// Dump the graph to a Uint8Array.</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> bytes</span><span class="token operator">:</span><span class="token plain"> Uint8Array </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">saveSnapshot</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token comment">// Persist the bytes wherever you already store state — IndexedDB,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token comment">// localStorage, a POST to your backend, `fs.writeFileSync` in Node.</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token comment">// Later:</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> db2 </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword">await</span><span class="token plain"> </span><span class="token function">createDatabase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">await</span><span class="token plain"> db2</span><span class="token punctuation">.</span><span class="token function">loadSnapshot</span><span class="token punctuation">(</span><span class="token plain">bytes</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></div></code></pre></div></div>
<p><code>saveSnapshot</code> can also return <code>ArrayBuffer</code>, <code>Blob</code>, <code>Response</code>,
<code>ReadableStream</code>, or an object <code>URL</code>; <code>loadSnapshot</code> accepts <code>URL</code>,
<code>Uint8Array</code>, <code>ArrayBuffer</code>, <code>Blob</code>, <code>Response</code>, or
<code>ReadableStream&lt;Uint8Array | ArrayBuffer&gt;</code>. The Worker-backed surface
(<code>createWorkerDatabase</code>) exposes the same <code>saveSnapshot</code> /
<code>loadSnapshot</code> methods.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="save-and-load-from-go">Save and load from Go<a href="https://loradb.com/blog/loradb-v0-3-snapshots#save-and-load-from-go" class="hash-link" aria-label="Direct link to Save and load from Go" title="Direct link to Save and load from Go" translate="no">​</a></h3>
<div class="language-go codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-go codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token keyword">import</span><span class="token plain"> lora </span><span class="token string">"github.com/lora-db/lora/crates/bindings/lora-go"</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain">db</span><span class="token punctuation">,</span><span class="token plain"> err </span><span class="token operator">:=</span><span class="token plain"> lora</span><span class="token punctuation">.</span><span class="token function">New</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">if</span><span class="token plain"> err </span><span class="token operator">!=</span><span class="token plain"> </span><span class="token boolean">nil</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> log</span><span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span><span class="token plain">err</span><span class="token punctuation">)</span><span class="token plain"> </span><span class="token punctuation">}</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">defer</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">Close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">if</span><span class="token plain"> </span><span class="token boolean">_</span><span class="token punctuation">,</span><span class="token plain"> err </span><span class="token operator">:=</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">Execute</span><span class="token punctuation">(</span><span class="token string">"CREATE (:Person {name: 'Ada'})"</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token boolean">nil</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"> err </span><span class="token operator">!=</span><span class="token plain"> </span><span class="token boolean">nil</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    log</span><span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span><span class="token plain">err</span><span class="token punctuation">)</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token punctuation">}</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain">meta</span><span class="token punctuation">,</span><span class="token plain"> err </span><span class="token operator">:=</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">SaveSnapshot</span><span class="token punctuation">(</span><span class="token string">"graph.bin"</span><span class="token punctuation">)</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">if</span><span class="token plain"> err </span><span class="token operator">!=</span><span class="token plain"> </span><span class="token boolean">nil</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> log</span><span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span><span class="token plain">err</span><span class="token punctuation">)</span><span class="token plain"> </span><span class="token punctuation">}</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">fmt</span><span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"nodes=%d rels=%d\n"</span><span class="token punctuation">,</span><span class="token plain"> meta</span><span class="token punctuation">.</span><span class="token plain">NodeCount</span><span class="token punctuation">,</span><span class="token plain"> meta</span><span class="token punctuation">.</span><span class="token plain">RelationshipCount</span><span class="token punctuation">)</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain">db2</span><span class="token punctuation">,</span><span class="token plain"> err </span><span class="token operator">:=</span><span class="token plain"> lora</span><span class="token punctuation">.</span><span class="token function">New</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">if</span><span class="token plain"> err </span><span class="token operator">!=</span><span class="token plain"> </span><span class="token boolean">nil</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> log</span><span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span><span class="token plain">err</span><span class="token punctuation">)</span><span class="token plain"> </span><span class="token punctuation">}</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">defer</span><span class="token plain"> db2</span><span class="token punctuation">.</span><span class="token function">Close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">if</span><span class="token plain"> </span><span class="token boolean">_</span><span class="token punctuation">,</span><span class="token plain"> err </span><span class="token operator">:=</span><span class="token plain"> db2</span><span class="token punctuation">.</span><span class="token function">LoadSnapshot</span><span class="token punctuation">(</span><span class="token string">"graph.bin"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"> err </span><span class="token operator">!=</span><span class="token plain"> </span><span class="token boolean">nil</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    log</span><span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span><span class="token plain">err</span><span class="token punctuation">)</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token punctuation">}</span><br></div></code></pre></div></div>
<p>The Go FFI header (<code>crates/bindings/lora-go/include/lora_ffi.h</code>) now declares
<code>lora_db_save_snapshot</code> / <code>lora_db_load_snapshot</code> alongside a
<code>LoraSnapshotMeta</code> struct; the Go wrapper turns that into an idiomatic
<code>*SnapshotMeta</code> with a nullable <code>WalLsn</code> pointer.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="restoring-and-saving-through-the-http-server">Restoring And Saving Through The HTTP Server<a href="https://loradb.com/blog/loradb-v0-3-snapshots#restoring-and-saving-through-the-http-server" class="hash-link" aria-label="Direct link to Restoring And Saving Through The HTTP Server" title="Direct link to Restoring And Saving Through The HTTP Server" translate="no">​</a></h2>
<p><code>lora-server</code> exposes two opt-in admin endpoints for snapshot
operations. They do not exist unless the server is started with
<code>--snapshot-path</code>:</p>
<div class="language-bash codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-bash codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token plain">lora-server \</span><br></div><div class="token-line"><span class="token plain">  --host 127.0.0.1 --port 4747 \</span><br></div><div class="token-line"><span class="token plain">  --snapshot-path /var/lib/lora/db.bin \</span><br></div><div class="token-line"><span class="token plain">  --restore-from  /var/lib/lora/db.bin</span><br></div></code></pre></div></div>
<ul>
<li class=""><code>--snapshot-path &lt;PATH&gt;</code> mounts <code>POST /admin/snapshot/save</code> and
<code>POST /admin/snapshot/load</code> against this file. Without the flag the
routes return <code>404</code> — the admin surface is off by default.</li>
<li class=""><code>--restore-from &lt;PATH&gt;</code> loads a snapshot at boot before the server
accepts queries. A missing file is fine (empty graph, logged); a
malformed file is fatal.</li>
</ul>
<p>Once enabled, saving and restoring is a plain HTTP call:</p>
<div class="language-bash codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-bash codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token plain">curl -sX POST http://127.0.0.1:4747/admin/snapshot/save</span><br></div><div class="token-line"><span class="token plain"># =&gt; {"formatVersion":1,"nodeCount":1024,"relationshipCount":4096,"walLsn":null,"path":"/var/lib/lora/db.bin"}</span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain">curl -sX POST http://127.0.0.1:4747/admin/snapshot/load</span><br></div></code></pre></div></div>
<p>Both endpoints accept an optional <code>{ "path": "…" }</code> body to override
the configured default for a single request — useful for ad-hoc
backups to a rotated filename:</p>
<div class="language-bash codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-bash codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token plain">curl -sX POST http://127.0.0.1:4747/admin/snapshot/save \</span><br></div><div class="token-line"><span class="token plain">  -H 'content-type: application/json' \</span><br></div><div class="token-line"><span class="token plain">  -d '{"path": "/var/backups/lora/2026-04-24.bin"}'</span><br></div></code></pre></div></div>
<p><code>--restore-from</code> is independent of <code>--snapshot-path</code>. You can restore
from a read-only seed and save to a writable runtime path:</p>
<div class="language-bash codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-bash codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token plain">lora-server \</span><br></div><div class="token-line"><span class="token plain">  --restore-from  /var/lib/lora/seed.bin \</span><br></div><div class="token-line"><span class="token plain">  --snapshot-path /var/lib/lora/runtime.bin</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-caution admonition_IZjC alert alert--warning"><div class="admonitionHeading_uVvU"><span class="admonitionIcon_HiR3"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>Security</div><div class="admonitionContent_bl22"><p>The admin endpoints have <strong>no authentication</strong>, and the optional
<code>path</code> body field is passed straight to the OS. Any client that can
reach the admin port can write files anywhere the server UID can
write, or swap the live graph by pointing <code>load</code> at an attacker-staged
file. Do not expose the admin surface on a network-reachable host
without authenticated ingress in front (a reverse proxy with auth, a
Unix socket, or simply not binding the port at all). Future releases
may add authentication; until then, the correct deployment is "admin
surface disabled by default, enabled only behind an auth boundary".</p></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="why-snapshots-are-useful-with-or-without-a-wal">Why Snapshots Are Useful With Or Without A WAL<a href="https://loradb.com/blog/loradb-v0-3-snapshots#why-snapshots-are-useful-with-or-without-a-wal" class="hash-link" aria-label="Direct link to Why Snapshots Are Useful With Or Without A WAL" title="Direct link to Why Snapshots Are Useful With Or Without A WAL" translate="no">​</a></h2>
<p>A snapshot is not a replacement for continuous durability, but it
closes enough of the gap for many workloads. When paired with WAL,
snapshots become the checkpoint artifact that keeps replay bounded:</p>
<ul>
<li class=""><strong>Seeded services.</strong> Build the graph offline from a cheaper source
(SQL exports, a scrape, an ETL job), snapshot it, and ship the
snapshot alongside the deployment. Every restart boots in one
file-read rather than a multi-minute replay.</li>
<li class=""><strong>Notebooks and research tooling.</strong> Save the graph you've curated at
the end of a session; reload it the next morning with one call.</li>
<li class=""><strong>Agents and LLM context stores.</strong> Periodic snapshots of the working
graph give you trivial "go back to yesterday's state" without the
complexity of a full transactional store.</li>
<li class=""><strong>HTTP operator loop.</strong> <code>ExecStop=curl … /admin/snapshot/save</code> on a
systemd unit gives a graceful-shutdown save without any new
tooling. Add a <code>--restore-from</code> on boot and you have a durable-
enough deployment for a single-node service.</li>
<li class=""><strong>Scheduled backups.</strong> A cron that calls <code>POST /admin/snapshot/save</code> every N minutes, optionally with a rotating
<code>{"path": "…"}</code>, is a complete backup policy for small graphs.</li>
</ul>
<p>The bright line is still the same: a crash between saves loses every
mutation in the window. The question to ask is whether that window is
narrow enough for your workload. For most of the shapes above, it is.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="whats-still-out-of-scope">What's Still Out Of Scope<a href="https://loradb.com/blog/loradb-v0-3-snapshots#whats-still-out-of-scope" class="hash-link" aria-label="Direct link to What's Still Out Of Scope" title="Direct link to What's Still Out Of Scope" translate="no">​</a></h2>
<p>Explicitly not in this release, so the feature stays honest about its
boundary:</p>
<ul>
<li class=""><strong>Snapshots alone are still not a WAL.</strong> Current releases have
WAL-backed recovery on filesystem-backed surfaces, but a manual
snapshot by itself is still only point-in-time persistence.</li>
<li class=""><strong>No wall-clock scheduler.</strong> Manual snapshots run when you call them.
Raw-WAL helpers can write managed snapshots after N committed
transactions; wall-clock scheduling is still host/operator work.</li>
<li class=""><strong>No partial / incremental snapshots.</strong> A save serializes the whole
graph. For v0.3 the expected scale is graphs that fit in memory
comfortably and dump in seconds.</li>
<li class=""><strong>Non-blocking save.</strong> The store mutex is held for the full save.
Concurrent queries block. Real per-mutation copy-on-write will come
with deeper storage-engine work.</li>
<li class=""><strong>No multi-graph file format.</strong> One file, one graph — same one-process
model as the rest of the engine.</li>
<li class=""><strong>No auth on the HTTP admin surface.</strong> Opt-in, off by default, and
still not safe on a network-reachable host without an ingress.</li>
</ul>
<p>Those are the things a future release will address. They are not
hidden in the implementation — every one of them is a place the docs
say so.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="try-it">Try It<a href="https://loradb.com/blog/loradb-v0-3-snapshots#try-it" class="hash-link" aria-label="Direct link to Try It" title="Direct link to Try It" translate="no">​</a></h2>
<p>Get the repo, build, and snapshot:</p>
<div class="language-bash codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-bash codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token plain">cargo run --bin lora-server -- \</span><br></div><div class="token-line"><span class="token plain">  --snapshot-path /tmp/loradb.bin \</span><br></div><div class="token-line"><span class="token plain">  --restore-from  /tmp/loradb.bin</span><br></div></code></pre></div></div>
<p>Then from a second shell:</p>
<div class="language-bash codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-bash codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token plain">curl -sX POST http://127.0.0.1:4747/query \</span><br></div><div class="token-line"><span class="token plain">  -H 'content-type: application/json' \</span><br></div><div class="token-line"><span class="token plain">  -d '{"query":"CREATE (:Person {name:\"Ada\"})"}' &gt; /dev/null</span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain">curl -sX POST http://127.0.0.1:4747/admin/snapshot/save</span><br></div><div class="token-line"><span class="token plain"># =&gt; {"formatVersion":1,"nodeCount":1,"relationshipCount":0,"walLsn":null,"path":"/tmp/loradb.bin"}</span><br></div></code></pre></div></div>
<p>Stop the server, start it again with the same flags, and the graph is
still there.</p>
<p>The docs site has a dedicated page for snapshots — the file format,
atomicity guarantees, binding examples, and the full HTTP admin
surface:</p>
<ul>
<li class=""><a class="" href="https://loradb.com/docs/snapshot">Snapshots</a></li>
<li class=""><a class="" href="https://loradb.com/docs/getting-started/server#snapshots-wal-and-restore">HTTP server quickstart → Snapshots, WAL, and restore</a></li>
<li class=""><a class="" href="https://loradb.com/docs/api/http#admin-endpoints-opt-in">HTTP API → Admin endpoints (opt-in)</a></li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-comes-next">What Comes Next<a href="https://loradb.com/blog/loradb-v0-3-snapshots#what-comes-next" class="hash-link" aria-label="Direct link to What Comes Next" title="Direct link to What Comes Next" translate="no">​</a></h2>
<p>Three directions stood out after v0.3:</p>
<ol>
<li class=""><strong>A WAL.</strong> This has since landed on every filesystem-backed
surface, with Rust and <code>lora-server</code> exposing the full operator
controls and embedded bindings exposing raw-WAL helpers.</li>
<li class=""><strong>Checkpoint automation.</strong> Current raw-WAL helpers can write
managed snapshots after N committed transactions. Wall-clock
scheduling remains a host/operator concern.</li>
<li class=""><strong>Auth on the admin surface.</strong> Token-based auth in front of
<code>/admin/*</code> so the endpoints can be used on network-reachable hosts
without an external reverse proxy.</li>
</ol>
<p>If you try v0.3 with snapshots, the feedback that will shape those is
concrete:</p>
<ul>
<li class="">how large does your graph get, and how long does <code>save_snapshot</code>
take at that size;</li>
<li class="">what cadence did you end up running — seconds, minutes, on shutdown
only;</li>
<li class="">did the atomic-rename guarantee land cleanly on your filesystem
(we've tested on Linux ext4/xfs and macOS APFS);</li>
<li class="">what does your ingress look like for the admin endpoints;</li>
<li class="">which binding did you use, and did the byte-based WASM surface fit
your storage layer (IndexedDB, OPFS, a backend POST) without extra
glue.</li>
</ul>
<p>That is the feedback that will shape v0.4.</p>]]></content:encoded>
            <category>Release notes</category>
            <category>Announcement</category>
            <category>Persistence</category>
            <category>Operations</category>
        </item>
        <item>
            <title><![CDATA[LoraDB v0.2: vector values for connected AI context]]></title>
            <link>https://loradb.com/blog/loradb-v0-2-vectors</link>
            <guid>https://loradb.com/blog/loradb-v0-2-vectors</guid>
            <pubDate>Thu, 23 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[LoraDB v0.2 adds first-class VECTOR values, vector functions, binding support, and documentation for graph-shaped AI retrieval on top of the v0.1 core.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="LoraDB v0.2 — first-class VECTOR values for connected AI context." src="https://loradb.com/assets/images/loradb-v0-2-vectors-header-14c2b003e951ab0403f41ec2b42615db.png" width="1280" height="400" class="img__Ss2"></p>
<p>LoraDB v0.2 adds first-class <code>VECTOR</code> values.</p>
<p>You can now construct vectors in Cypher, store them as node or
relationship properties, pass them in as parameters through every
binding, and run exhaustive similarity search against them. The value
type, the wire format, the function surface, and the binding helpers
all landed together so vectors behave like every other typed value in
the engine.</p>
<p>What this release is not is a vector-index product. There is no
approximate nearest-neighbour search, no built-in embedding
generation, and no plugin compatibility layer. Those are deliberately
out of scope for v0.2. The goal here is to make embeddings comfortable
inside the graph model — to ship the foundation that an index-backed
retrieval path will eventually sit on.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-changed">What Changed<a href="https://loradb.com/blog/loradb-v0-2-vectors#what-changed" class="hash-link" aria-label="Direct link to What Changed" title="Direct link to What Changed" translate="no">​</a></h2>
<p>The short list:</p>
<ul>
<li class="">
<p><code>VECTOR</code> is a first-class value type, alongside scalars, lists,
maps, temporal values, and spatial points.</p>
</li>
<li class="">
<p>Cast-based vector construction with <code>value::VECTOR&lt;COORD&gt;(DIM)</code>.</p>
</li>
<li class="">
<p>Six supported coordinate types:</p>
<ul>
<li class=""><code>FLOAT</code> / <code>FLOAT64</code></li>
<li class=""><code>FLOAT32</code></li>
<li class=""><code>INTEGER</code> / <code>INT</code> / <code>INT64</code> / <code>INTEGER64</code></li>
<li class=""><code>INTEGER32</code> / <code>INT32</code></li>
<li class=""><code>INTEGER16</code> / <code>INT16</code></li>
<li class=""><code>INTEGER8</code> / <code>INT8</code></li>
</ul>
</li>
<li class="">
<p>Storage as node and relationship properties.</p>
</li>
<li class="">
<p><code>vector.coordinates(v, INTEGER)</code> and <code>vector.coordinates(v, FLOAT)</code>
for converting coordinates back to lists.</p>
</li>
<li class="">
<p><code>vector.dimension(v)</code> and <code>value.size(v)</code> for introspection.</p>
</li>
<li class="">
<p><code>vector.similarity(a, b)</code> — cosine similarity bounded to <code>[0, 1]</code>.</p>
</li>
<li class="">
<p><code>vector.similarity(a, b, 'euclidean')</code> — Euclidean similarity bounded to <code>[0, 1]</code>.</p>
</li>
<li class="">
<p><code>vector.distance(a, b, metric)</code> — signed distance under one of six
metrics.</p>
</li>
<li class="">
<p><code>vector.norm(v, metric)</code> — Euclidean or Manhattan norm.</p>
</li>
<li class="">
<p>Parameter support through every binding.</p>
</li>
<li class="">
<p>A canonical tagged wire shape:</p>
<div class="language-json codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-json codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token punctuation">{</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token property">"kind"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"vector"</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token property">"dimension"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token property">"coordinateType"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"FLOAT32"</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token property">"values"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation">[</span><span class="token number">0.1</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">0.2</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">0.3</span><span class="token punctuation">]</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token punctuation">}</span><br></div></code></pre></div></div>
</li>
</ul>
<p>Language support is included in this release for:</p>
<ul>
<li class="">the Rust core;</li>
<li class="">Node.js / TypeScript (<code>crates/bindings/lora-node</code>);</li>
<li class="">WebAssembly (<code>crates/bindings/lora-wasm</code>);</li>
<li class="">Python (<code>crates/bindings/lora-python</code>);</li>
<li class="">Go (<code>crates/bindings/lora-go</code>);</li>
<li class="">Ruby (<code>crates/bindings/lora-ruby</code>).</li>
</ul>
<p>Each binding ships a <code>vector(...)</code> helper and an <code>isVector</code> /
<code>is_vector</code> / <code>IsVector</code> / <code>vector?</code> guard, so callers do not need to
build the tagged object by hand.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="why-vectors-in-a-graph-database">Why Vectors In A Graph Database<a href="https://loradb.com/blog/loradb-v0-2-vectors#why-vectors-in-a-graph-database" class="hash-link" aria-label="Direct link to Why Vectors In A Graph Database" title="Direct link to Why Vectors In A Graph Database" translate="no">​</a></h2>
<p>A graph database and a vector store usually get treated as two
products. The graph stores relationships; the vector store retrieves
similar items; the application glues them together.</p>
<p>For most AI workloads that is the wrong shape. You want to retrieve
candidates by similarity <em>and</em> use the graph to rank, filter, or
explain them. When the embedding lives on a separate service, every
query needs a round trip and every piece of context needs a join by
hand.</p>
<p>Putting <code>VECTOR</code> into LoraDB as a value type collapses that
separation. The embedding is a property on the same node that carries
the label, the text, and the relationships. You score with
<code>vector.similarity(...)</code> and walk with <code>MATCH</code> in the same
Cypher.</p>
<p>That is the whole argument. Similarity finds candidates. The graph
explains them.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="using-vector-values">Using VECTOR Values<a href="https://loradb.com/blog/loradb-v0-2-vectors#using-vector-values" class="hash-link" aria-label="Direct link to Using VECTOR Values" title="Direct link to Using VECTOR Values" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="creating-a-vector-property">Creating A Vector Property<a href="https://loradb.com/blog/loradb-v0-2-vectors#creating-a-vector-property" class="hash-link" aria-label="Direct link to Creating A Vector Property" title="Direct link to Creating A Vector Property" translate="no">​</a></h3>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">CREATE (d:Doc {
  id:        1,
  title:     'Onboarding checklist',
  embedding: [0.1, 0.2, 0.3]::VECTOR&lt;FLOAT32&gt;(3)
})</code></pre></div>
<p>The coordinate tag and dimension live in the target type:
<code>VECTOR&lt;FLOAT32&gt;(3)</code>, <code>VECTOR&lt;INTEGER&gt;(384)</code>, and so on. <code>CAST(value AS VECTOR&lt;COORD&gt;(DIM))</code> is the equivalent compatibility form.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="passing-a-vector-as-a-parameter">Passing A Vector As A Parameter<a href="https://loradb.com/blog/loradb-v0-2-vectors#passing-a-vector-as-a-parameter" class="hash-link" aria-label="Direct link to Passing A Vector As A Parameter" title="Direct link to Passing A Vector As A Parameter" translate="no">​</a></h3>
<p>Generate the embedding in your application, pass it in with the
language helper, and use it like any other parameter:</p>
<div class="language-ts codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-ts codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token keyword">import</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> vector </span><span class="token punctuation">}</span><span class="token plain"> </span><span class="token keyword">from</span><span class="token plain"> </span><span class="token string">"@loradb/lora-node"</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> query </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function">vector</span><span class="token punctuation">(</span><span class="token plain">embedding</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">384</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token string">"FLOAT32"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token template-string template-punctuation string">`</span><span class="token template-string string">MATCH (d:Doc)</span><br></div><div class="token-line"><span class="token template-string string">   RETURN d.id AS id</span><br></div><div class="token-line"><span class="token template-string string">   ORDER BY vector.similarity(d.embedding, $q) DESC</span><br></div><div class="token-line"><span class="token template-string string">   LIMIT 10</span><span class="token template-string template-punctuation string">`</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token punctuation">{</span><span class="token plain"> q</span><span class="token operator">:</span><span class="token plain"> query </span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></div></code></pre></div></div>
<p>The same pattern works in Python (<code>vector(values, dimension, coordinate_type)</code>), Go (<code>lora.Vector(values, dimension, coordinateType)</code>),
Ruby (<code>LoraRuby.vector(values, dimension, coordinate_type)</code>), and the
raw FFI / JSON path used by the C ABI.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="bulk-insert">Bulk Insert<a href="https://loradb.com/blog/loradb-v0-2-vectors#bulk-insert" class="hash-link" aria-label="Direct link to Bulk Insert" title="Direct link to Bulk Insert" translate="no">​</a></h3>
<p>The canonical way to load many embeddings is a single <code>UNWIND</code> over a
parameter list of rows. Each row is a map with scalar fields and a
tagged vector. The engine fans the list into per-row <code>CREATE</code>s without
round-tripping each one:</p>
<div class="language-ts codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-ts codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token keyword">import</span><span class="token plain"> </span><span class="token punctuation">{</span><span class="token plain"> vector </span><span class="token punctuation">}</span><span class="token plain"> </span><span class="token keyword">from</span><span class="token plain"> </span><span class="token string">"@loradb/lora-node"</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">const</span><span class="token plain"> batch </span><span class="token operator">=</span><span class="token plain"> docs</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token plain">doc</span><span class="token punctuation">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  id</span><span class="token operator">:</span><span class="token plain">        doc</span><span class="token punctuation">.</span><span class="token plain">id</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  title</span><span class="token operator">:</span><span class="token plain">     doc</span><span class="token punctuation">.</span><span class="token plain">title</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  embedding</span><span class="token operator">:</span><span class="token plain"> </span><span class="token function">vector</span><span class="token punctuation">(</span><span class="token plain">doc</span><span class="token punctuation">.</span><span class="token plain">embedding</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">384</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token string">"FLOAT32"</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">await</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token template-string template-punctuation string">`</span><span class="token template-string string">UNWIND $batch AS row</span><br></div><div class="token-line"><span class="token template-string string">   CREATE (:Doc {id: row.id, title: row.title, embedding: row.embedding})</span><span class="token template-string template-punctuation string">`</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token punctuation">{</span><span class="token plain"> batch </span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></div></code></pre></div></div>
<p>The same shape in Python:</p>
<div class="language-python codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-python codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token keyword">from</span><span class="token plain"> lora_python </span><span class="token keyword">import</span><span class="token plain"> Database</span><span class="token punctuation">,</span><span class="token plain"> vector</span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain">db </span><span class="token operator">=</span><span class="token plain"> Database</span><span class="token punctuation">.</span><span class="token plain">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain">batch </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation">[</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token punctuation">{</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">        </span><span class="token string">"id"</span><span class="token punctuation">:</span><span class="token plain">        doc</span><span class="token punctuation">[</span><span class="token string">"id"</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">        </span><span class="token string">"title"</span><span class="token punctuation">:</span><span class="token plain">     doc</span><span class="token punctuation">[</span><span class="token string">"title"</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">        </span><span class="token string">"embedding"</span><span class="token punctuation">:</span><span class="token plain"> vector</span><span class="token punctuation">(</span><span class="token plain">doc</span><span class="token punctuation">[</span><span class="token string">"embedding"</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">384</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token string">"FLOAT32"</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token punctuation">}</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token keyword">for</span><span class="token plain"> doc </span><span class="token keyword">in</span><span class="token plain"> docs</span><br></div><div class="token-line"><span class="token plain"></span><span class="token punctuation">]</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain">db</span><span class="token punctuation">.</span><span class="token plain">execute</span><span class="token punctuation">(</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token triple-quoted-string string">"""</span><br></div><div class="token-line"><span class="token triple-quoted-string string">    UNWIND $batch AS row</span><br></div><div class="token-line"><span class="token triple-quoted-string string">    CREATE (:Doc {id: row.id, title: row.title, embedding: row.embedding})</span><br></div><div class="token-line"><span class="token triple-quoted-string string">    """</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">    </span><span class="token punctuation">{</span><span class="token string">"batch"</span><span class="token punctuation">:</span><span class="token plain"> batch</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token punctuation">)</span><br></div></code></pre></div></div>
<p>Two things are worth knowing about this pattern. First, each vector is
stored as its own property on its own node — the batch is a list of
<em>maps</em>, not a list of vectors, so the property rule (no lists of
vectors) is satisfied by construction. Second, <code>UNWIND</code> runs the whole
batch in one query, so the per-row overhead is a map extraction and a
<code>CREATE</code>, not a full parse + plan + execute cycle per document.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="exhaustive-knn">Exhaustive kNN<a href="https://loradb.com/blog/loradb-v0-2-vectors#exhaustive-knn" class="hash-link" aria-label="Direct link to Exhaustive kNN" title="Direct link to Exhaustive kNN" translate="no">​</a></h3>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">MATCH (d:Doc)
RETURN d.id AS id
ORDER BY vector.similarity(d.embedding, $query) DESC
LIMIT 10</code></pre></div>
<p>Every <code>MATCH</code> candidate is scored. Cost is <code>O(n)</code> in the number of
matched nodes. That is fine for a local dataset, a test, a demo, or a
small internal tool. It is not how you would serve a corpus of
millions — that is what vector indexes are for, and they are not
implemented yet.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="graph-filtered-retrieval">Graph-Filtered Retrieval<a href="https://loradb.com/blog/loradb-v0-2-vectors#graph-filtered-retrieval" class="hash-link" aria-label="Direct link to Graph-Filtered Retrieval" title="Direct link to Graph-Filtered Retrieval" translate="no">​</a></h3>
<p>The version that actually motivated adding vectors to LoraDB looks
like this:</p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">MATCH (d:Doc)
WITH d, vector.similarity(d.embedding, $query) AS score
MATCH (d)-[:MENTIONS]-&gt;(e:Entity)
WHERE e.type = $entity_type
RETURN d.id, d.title, score, collect(e.name) AS entities
ORDER BY score DESC
LIMIT 5</code></pre></div>
<p>The similarity function supplies the candidates. The graph structure
(<code>MENTIONS</code>, the entity type filter) explains and constrains them.
Both live in one query and one engine.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="storing-and-reading-back">Storing And Reading Back<a href="https://loradb.com/blog/loradb-v0-2-vectors#storing-and-reading-back" class="hash-link" aria-label="Direct link to Storing And Reading Back" title="Direct link to Storing And Reading Back" translate="no">​</a></h3>
<p>Vectors round-trip through storage unchanged:</p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">CREATE (:Doc {id: 1, embedding: [1, 2, 3]::VECTOR&lt;INTEGER&gt;(3)})
MATCH (d:Doc {id: 1}) SET d.embedding = [0.1, 0.2]::VECTOR&lt;FLOAT32&gt;(2)
MATCH (d:Doc {id: 1}) RETURN d.embedding AS e</code></pre></div>
<p>A vector is also a legal map value, so a property map containing a
vector is stored intact. The one restriction is that <strong>a list
containing vectors cannot be stored as a property</strong> — if you need
many embeddings, hang them off separate nodes. The engine rejects the
write at property-conversion time instead of silently storing a shape
a future vector index could not support.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="ai-use-cases">AI Use Cases<a href="https://loradb.com/blog/loradb-v0-2-vectors#ai-use-cases" class="hash-link" aria-label="Direct link to AI Use Cases" title="Direct link to AI Use Cases" translate="no">​</a></h2>
<p>Vectors in LoraDB are aimed at the workloads that already sit
awkwardly between "vector store" and "graph database":</p>
<ul>
<li class=""><strong>Agent memory.</strong> Embed documents, chunks, observations, or tool
calls. Connect them with edges to the sessions, entities, and
decisions that reference them. Retrieve by similarity, filter by
recency, explain by provenance.</li>
<li class=""><strong>Semantic document retrieval with context.</strong> Find the k closest
docs, then expand to the entities, authors, or topics they connect
to before returning anything to the application.</li>
<li class=""><strong>Tool and context selection.</strong> Score candidate prompts, tools, or
examples by similarity to the current state, then filter by graph
constraints (same tenant, same permission scope, same task type).</li>
<li class=""><strong>Knowledge graph enrichment.</strong> Attach embeddings to entities so
fuzzy lookups can locate the right node before structural queries
run.</li>
<li class=""><strong>Recommendations with graph guardrails.</strong> Cosine similarity
produces a long list of candidates; graph relationships
(<code>BLOCKED</code>, <code>ALREADY_SEEN</code>, <code>OWNED_BY</code>) filter that list before
ranking.</li>
<li class=""><strong>Internal search over connected product, customer, or support
data.</strong> Small corpora, high structure, and queries that want both
"similar to this ticket" and "in the same account and escalation
path."</li>
</ul>
<p>Every one of those workloads has the same shape: similarity for
candidates, structure for the rest.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-is-not-included-yet">What Is Not Included Yet<a href="https://loradb.com/blog/loradb-v0-2-vectors#what-is-not-included-yet" class="hash-link" aria-label="Direct link to What Is Not Included Yet" title="Direct link to What Is Not Included Yet" translate="no">​</a></h2>
<p>This release is deliberately narrow. It does not include:</p>
<ul>
<li class=""><strong>Vector indexes.</strong> All vector functions are exhaustive today.</li>
<li class=""><strong>Approximate nearest-neighbour search.</strong> There is no ANN path.</li>
<li class=""><strong>Built-in embedding generation.</strong> LoraDB does not produce
embeddings; bring them in from your application code.</li>
<li class=""><strong>Hardened public-internet database hosting.</strong> The HTTP server is
useful for local development and controlled environments. It is
not a managed service.</li>
</ul>
<p>Those are not hidden. They are the roadmap.</p>
<p>Exhaustive similarity is fine for small datasets, tests, demos, local
prototypes, and internal workflows. It is not yet a substitute for an
index-backed vector search service at scale.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="why-this-fits-the-loradb-journey">Why This Fits The LoraDB Journey<a href="https://loradb.com/blog/loradb-v0-2-vectors#why-this-fits-the-loradb-journey" class="hash-link" aria-label="Direct link to Why This Fits The LoraDB Journey" title="Direct link to Why This Fits The LoraDB Journey" translate="no">​</a></h2>
<p>LoraDB is developer-first.</p>
<p>That sequencing applies to vectors too. The first version needs the
value model, the wire shape, the binding helpers, and the Cypher
ergonomics to be right. Those are the parts that user code and tests
depend on. A vector index that sits on a broken value model would
inherit every problem. Shipping the foundation first, and being
honest about the absence of an index, keeps the upgrade path clean.</p>
<p>It also matches how developers actually pick up a new capability.
Clone the repo, generate a few embeddings, store them on nodes, write
a similarity query, see whether the model fits the problem. Only
after that does scale, persistence, and managed operations become
worth talking about.</p>
<p>v0.2 makes that first loop work.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="try-it">Try It<a href="https://loradb.com/blog/loradb-v0-2-vectors#try-it" class="hash-link" aria-label="Direct link to Try It" title="Direct link to Try It" translate="no">​</a></h2>
<p>Get the repo and run the server:</p>
<div class="language-bash codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-bash codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token plain">cargo run --bin lora-server</span><br></div></code></pre></div></div>
<p>Then try a vector query from <code>curl</code> or any binding:</p>
<div class="language-bash codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-bash codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token plain">curl -X POST http://127.0.0.1:4747/query \</span><br></div><div class="token-line"><span class="token plain">  -H 'content-type: application/json' \</span><br></div><div class="token-line"><span class="token plain">  -d '{"query":"RETURN [1,2,3]::VECTOR&lt;INTEGER&gt;(3) AS v"}'</span><br></div></code></pre></div></div>
<p>The docs site has a dedicated page for the value type, the coordinate
rules, the functions, the storage restrictions, and the exhaustive
kNN pattern:</p>
<ul>
<li class=""><a class="" href="https://loradb.com/docs/data-types/vectors">Data types → Vectors</a></li>
<li class=""><a class="" href="https://loradb.com/docs/functions/overview">Functions → Overview</a></li>
<li class=""><a class="" href="https://loradb.com/docs/queries/parameters">Queries → Parameters</a></li>
</ul>
<p>Internal notes on the value model and the Cypher support matrix have
been updated to match.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-comes-next">What Comes Next<a href="https://loradb.com/blog/loradb-v0-2-vectors#what-comes-next" class="hash-link" aria-label="Direct link to What Comes Next" title="Direct link to What Comes Next" translate="no">​</a></h2>
<p>Three directions stand out after v0.2:</p>
<ol>
<li class=""><strong>A vector index.</strong> The Cypher shape stays the same
(<code>ORDER BY vector.similarity(...) LIMIT k</code>); the executor starts
routing scored candidates through an index instead of a linear
scan. The design depends on the workloads people actually bring.</li>
<li class=""><strong>More metrics and norms as real usage demands them.</strong> The
current set (<code>EUCLIDEAN</code>, <code>EUCLIDEAN_SQUARED</code>, <code>MANHATTAN</code>,
<code>COSINE</code>, <code>DOT</code>, <code>HAMMING</code> for distance; <code>EUCLIDEAN</code> and
<code>MANHATTAN</code> for norm) covers the common cases. Extending is
mechanical once a concrete need shows up.</li>
<li class=""><strong>The hosted path.</strong> Vectors on stored nodes eventually need
managed operations to match. That is a separate release, but the
value-type work here is what makes it possible without a second
data model.</li>
</ol>
<p>If you try v0.2 with vectors, the most useful feedback is concrete:</p>
<ul>
<li class="">what graph did you load, and what embedding workflow did you pair
with it;</li>
<li class="">where did exhaustive scan stop being enough;</li>
<li class="">what would a vector index need to support for your workload — a
specific metric, a specific filter shape, a specific freshness
guarantee;</li>
<li class="">which binding did you use, and did the tagged shape round-trip
cleanly through your application code.</li>
</ul>
<p>That is the feedback that will shape v0.3.</p>]]></content:encoded>
            <category>Release notes</category>
            <category>Announcement</category>
            <category>AI &amp; agents</category>
            <category>Cypher</category>
        </item>
        <item>
            <title><![CDATA[Vectors belong next to relationships]]></title>
            <link>https://loradb.com/blog/vectors-belong-next-to-relationships</link>
            <guid>https://loradb.com/blog/vectors-belong-next-to-relationships</guid>
            <pubDate>Wed, 22 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Why LoraDB adds VECTOR values: similarity is useful, but connected context is what makes retrieval explainable.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Vectors belong next to relationships — connected context makes retrieval explainable." src="https://loradb.com/assets/images/vectors-belong-next-to-relationships-header-089cc72fad5148a450820b59402abe90.png" width="1280" height="400" class="img__Ss2"></p>
<p>The conventional advice for AI retrieval is to pick a side.</p>
<p>You pick a vector database if you want similarity. You pick a graph
database if you want structure. You bolt them together with glue code
when the product inevitably needs both.</p>
<p>That framing has never matched the workloads I actually care about. The
interesting systems — agent memory, recommendations, internal search
over connected product data, knowledge graphs that feed chat features —
do not want a vector store <em>or</em> a graph store. They want to retrieve
candidates by similarity, then explain and filter those candidates by
relationships. Splitting that into two products splits the query path,
the data model, and eventually the team.</p>
<p>LoraDB v0.2 adds <a class="" href="https://loradb.com/docs/data-types/vectors"><code>VECTOR</code></a> as a first-class
value type. Vectors live directly on nodes and relationships, next to
labels, properties, and edges. The argument is not that a graph
database should replace a vector database. The argument is that
similarity belongs next to the relationships that give it meaning.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="similarity-is-useful-but-it-is-not-memory">Similarity Is Useful, But It Is Not Memory<a href="https://loradb.com/blog/vectors-belong-next-to-relationships#similarity-is-useful-but-it-is-not-memory" class="hash-link" aria-label="Direct link to Similarity Is Useful, But It Is Not Memory" title="Direct link to Similarity Is Useful, But It Is Not Memory" translate="no">​</a></h2>
<p>Embeddings are good at one thing: finding items that are close to a
query in some learned semantic space. That is genuinely useful. A lot
of retrieval systems live or die on whether the top ten candidates
contain the right answer.</p>
<p>But similarity alone does not preserve the information a product
usually needs <em>around</em> that answer:</p>
<ul>
<li class="">Where did this chunk come from?</li>
<li class="">Which entities does it mention?</li>
<li class="">Which session produced it?</li>
<li class="">What depends on it?</li>
<li class="">What contradicts it?</li>
<li class="">Is it more recent than the other candidates?</li>
<li class="">Is it from a trusted source?</li>
</ul>
<p>A flat list of similar chunks cannot answer those questions. The
relationships have to be recorded somewhere, and the system that
retrieves embeddings has to reach that somewhere cheaply. When those
two things live in different databases, "cheaply" stops being a local
property of the query engine.</p>
<p>That is why vectors belong next to the graph, not behind a sidecar.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-agent-memory-shape">The Agent Memory Shape<a href="https://loradb.com/blog/vectors-belong-next-to-relationships#the-agent-memory-shape" class="hash-link" aria-label="Direct link to The Agent Memory Shape" title="Direct link to The Agent Memory Shape" translate="no">​</a></h2>
<p>Think about what an agent actually stores across a session.</p>
<p>It stores documents or chunks, with embeddings so they can be
retrieved by similarity. It stores entities extracted from those
documents. It stores tool calls and their arguments. It stores
decisions, observations, and the context that led to each one. It
stores edges between those things: this observation led to that
decision; this tool was selected because of that memory; this document
mentions those entities.</p>
<p>That is a graph. And it is a graph with embeddings on some of the
nodes.</p>
<p>A small version looks like this:</p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">CREATE (d:Doc {
  id:        'doc-17',
  title:     'Onboarding checklist',
  embedding: $embedding::VECTOR&lt;FLOAT32&gt;(384)
})
CREATE (e:Entity {name: 'Alice'})
CREATE (d)-[:MENTIONS]-&gt;(e)
CREATE (o:Observation {session: 's1', ts: temporal.now()})
CREATE (o)-[:OBSERVED_IN]-&gt;(d)</code></pre></div>
<p>Later, when the agent needs to retrieve memory, the query is not "find
the ten closest chunks." The useful query is "find the ten closest
chunks, show which entities they mention, and give me enough structure
to rank or filter":</p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">MATCH (d:Doc)
WITH d, vector.similarity(d.embedding, $query) AS score
MATCH (d)-[:MENTIONS]-&gt;(e:Entity)
RETURN d.id, d.title, score, collect(e.name) AS entities
ORDER BY score DESC
LIMIT 5</code></pre></div>
<p>Similarity finds the candidates. The graph explains them.</p>
<p>That shape is easier to build — and easier to debug — when the
embedding is a property on the same node that carries the
relationships. There is no sync process, no second database, no
second query pipeline. There is one model.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="why-vector-is-a-value-type">Why VECTOR Is A Value Type<a href="https://loradb.com/blog/vectors-belong-next-to-relationships#why-vector-is-a-value-type" class="hash-link" aria-label="Direct link to Why VECTOR Is A Value Type" title="Direct link to Why VECTOR Is A Value Type" translate="no">​</a></h2>
<p>A vector in LoraDB is not a special table or a separate index
namespace. It is a value type, like a string or a point or a duration.
That design choice has consequences.</p>
<p>A <code>LoraVector</code> has:</p>
<ul>
<li class="">a fixed dimension (<code>1..=4096</code>);</li>
<li class="">a typed coordinate choice (<code>FLOAT64</code>, <code>FLOAT32</code>, <code>INTEGER</code>,
<code>INTEGER32</code>, <code>INTEGER16</code>, <code>INTEGER8</code>);</li>
<li class="">its coordinates stored in a single typed array.</li>
</ul>
<p>It can appear anywhere a value can appear:</p>
<ul>
<li class="">as a node property: <code>CREATE (:Doc {embedding: [...]::VECTOR&lt;FLOAT32&gt;(384)})</code></li>
<li class="">as a relationship property: <code>CREATE (a)-[:SIM {score: [...]::VECTOR&lt;FLOAT32&gt;(3)}]-&gt;(b)</code></li>
<li class="">as a Cypher parameter: pass a binding helper value or cast a list parameter in the query</li>
<li class="">inside a <code>RETURN</code>, <code>WITH</code>, <code>ORDER BY</code>, or <code>WHERE</code> clause.</li>
</ul>
<p>Every binding speaks the same canonical tagged shape:</p>
<div class="language-json codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-json codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token punctuation">{</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token property">"kind"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"vector"</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token property">"dimension"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token property">"coordinateType"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string">"FLOAT32"</span><span class="token punctuation">,</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain">  </span><span class="token property">"values"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation">[</span><span class="token number">0.1</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">0.2</span><span class="token punctuation">,</span><span class="token plain"> </span><span class="token number">0.3</span><span class="token punctuation">]</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token punctuation">}</span><br></div></code></pre></div></div>
<p>This is not only a wire format. It is the thing the Node.js, WASM,
Python, Go, and Ruby helpers build. A developer in any of those
languages can construct a vector in application code, pass it in as a
parameter, store it on a node, read it back, and get the same tagged
object on the other side. No bridge code. No JSON schemas to keep in
sync.</p>
<p>That value-type framing also forces a property-storage rule worth
stating out loud: a vector is fine as a property, but a <strong>list of
vectors</strong> is not. If you want many vectors, hang them off separate
nodes with separate embeddings. That restriction is enforced at write
time — the engine rejects the property instead of silently storing a
shape that future indexing could not support.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="why-exhaustive-first">Why Exhaustive First<a href="https://loradb.com/blog/vectors-belong-next-to-relationships#why-exhaustive-first" class="hash-link" aria-label="Direct link to Why Exhaustive First" title="Direct link to Why Exhaustive First" translate="no">​</a></h2>
<p>v0.2 ships vectors, but it does not ship vector indexes. That is a
deliberate line.</p>
<p>What works today is exhaustive similarity search. You write a <code>MATCH</code>
that produces a candidate set, you score every candidate with
<code>vector.similarity(...)</code> or <code>vector.distance(...)</code>,
you <code>ORDER BY score DESC LIMIT k</code>. The engine scans every matched
node.</p>
<p>For a graph of a few hundred thousand embeddings, this is completely
fine on a laptop. For millions, you want a proper index. The
difference is not in the query language — the <code>ORDER BY … LIMIT k</code>
shape is the same either way — it is in what the engine does under
the hood.</p>
<p>Shipping exhaustive first lets v0.2 land the parts that are hardest to
change later:</p>
<ul>
<li class="">the <code>VECTOR</code> value type and its coordinate rules;</li>
<li class="">the tagged wire shape that every binding speaks;</li>
<li class="">the property-storage semantics;</li>
<li class="">the function surface (<code>vector.similarity</code>, <code>vector.distance</code>,
<code>vector.norm</code>, <code>vector.coordinates</code>, <code>vector.dimension</code>,
<code>value.size(vector)</code>);</li>
<li class="">the Cypher ergonomics, including the bare-identifier rewrite that
lets you write <code>INTEGER8</code> and <code>EUCLIDEAN</code> without declaring them as
variables.</li>
</ul>
<p>Those are the decisions that bindings, tests, and user code depend
on. If we got any of them wrong, a vector index would inherit the
mistake. The order is: value model first, index-backed retrieval
later.</p>
<p>It also matches LoraDB's customer journey. The first question is not
"can this serve ten million embeddings?" The first question is "does
the model fit my problem?" An exhaustive scan over a thousand docs is
enough to answer that.</p>
<p>What is explicitly <em>not</em> included in v0.2 is worth stating plainly:</p>
<ul>
<li class="">no vector indexes;</li>
<li class="">no approximate nearest-neighbour search;</li>
<li class="">no built-in embedding generation;</li>
<li class="">no hardened public-internet database hosting.</li>
</ul>
<p>Generate your embeddings in application code — whatever you already
use, whether that is a hosted API, a local model, or a batch job —
and pass them in as parameters. The database's job is to store them
next to the graph and let you query both in one language.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-customer-journey-for-vectors">The Customer Journey For Vectors<a href="https://loradb.com/blog/vectors-belong-next-to-relationships#the-customer-journey-for-vectors" class="hash-link" aria-label="Direct link to The Customer Journey For Vectors" title="Direct link to The Customer Journey For Vectors" translate="no">​</a></h2>
<p>The flow I want for a developer adopting vectors in LoraDB mirrors the
one I wanted for adopting LoraDB at all.</p>
<ol>
<li class="">They have a retrieval problem that is not purely similarity —
there is structure around the items they want to find.</li>
<li class="">They generate embeddings in application code and store them on
graph nodes with a single <code>CREATE</code>.</li>
<li class="">They run <code>vector.similarity(...)</code> against a small local
dataset. The query shape is ordinary Cypher.</li>
<li class="">They add <code>MATCH</code> patterns that filter or explain the results using
relationships.</li>
<li class="">They build a prototype, a tool, or a product feature on that
combined query.</li>
<li class="">When the dataset grows past what an exhaustive scan can serve,
they move to an index-backed variant — without rewriting the
application, because the Cypher stays the same.</li>
<li class="">Eventually, managed operations follow.</li>
</ol>
<p>That is the same staircase as before. Local trust first, persistence
and platform later. Vectors fit because they slot into the same model
as everything else LoraDB stores.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="closing">Closing<a href="https://loradb.com/blog/vectors-belong-next-to-relationships#closing" class="hash-link" aria-label="Direct link to Closing" title="Direct link to Closing" translate="no">​</a></h2>
<p>Similarity helps you find candidates. Graphs help you explain them.</p>
<p>Most of the AI systems I care about — agent memory, internal search
over connected data, recommendations with guardrails, knowledge graphs
that feed chat — need both. The mistake is treating that as two
products. LoraDB v0.2 is the argument that it should be one.</p>
<p>The v0.2 release article has the full list of what landed, the
functions, the binding support, and the Cypher examples. The short
version is that <code>vector</code> is now a value you can put on a node, pass as
a parameter, and query with a few small honest functions. The longer
version is everything we are <em>not</em> trying to be — a vector-index
product, a hosted ANN service, a plugin marketplace — because the
first job is to make the graph model comfortable with embeddings
sitting on it.</p>
<p>If you try it, the feedback I want is concrete:</p>
<ul>
<li class="">what graph did you load, and what embedding workflow did you pair
with it;</li>
<li class="">what did the query look like once similarity and structure were in
the same Cypher;</li>
<li class="">at what size did the exhaustive scan stop being good enough;</li>
<li class="">what would a vector index need to support for your workload — a
specific metric, a specific filter shape, a specific freshness
guarantee?</li>
</ul>
<p>That is what will shape the next release.</p>
<hr>
<p>Canonical references:</p>
<ul>
<li class=""><a class="" href="https://loradb.com/docs/data-types/vectors">Data Types → Vectors</a> — construction, storage,
and the list-of-vectors property restriction.</li>
<li class=""><a class="" href="https://loradb.com/docs/functions/overview">Functions → Overview</a> — the vector similarity
and distance functions used above.</li>
<li class=""><a class="" href="https://loradb.com/docs/queries/parameters#semantic-retrieval-with-a-vector-parameter">Queries → Parameters</a>
— how each binding passes a <code>VECTOR</code> in.</li>
<li class=""><a class="" href="https://loradb.com/docs/cookbook#vector-retrieval-patterns">Cookbook → Vector-retrieval patterns</a>
— top-k and graph-filtered retrieval recipes.</li>
<li class=""><a class="" href="https://loradb.com/docs/limitations#vectors">Limitations → Vectors</a> — flat-scan vector
index procedures, no ANN structure, no embedding generation today.</li>
</ul>]]></content:encoded>
            <category>Founder notes</category>
            <category>AI &amp; agents</category>
            <category>Design</category>
            <category>Cypher</category>
        </item>
        <item>
            <title><![CDATA[LoraDB public release: a fast in-memory graph database in Rust]]></title>
            <link>https://loradb.com/blog/loradb-public-release</link>
            <guid>https://loradb.com/blog/loradb-public-release</guid>
            <pubDate>Sun, 19 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The first public release of LoraDB, what is included, how to try it, the license model, and where the project goes next.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="LoraDB public release v0.1 — a fast in-memory graph database in Rust." src="https://loradb.com/assets/images/loradb-public-release-header-7230a90e4c3ef4e2b804811f2433280c.png" width="1280" height="400" class="img__Ss2"></p>
<p>LoraDB is now public.</p>
<p>It is a fast in-memory graph database written in Rust, with a Cypher-shaped
query engine, an HTTP API, and bindings for Node.js, WebAssembly, and Python.
It is built for developers who need relationship queries close to their
application without adopting a large graph database stack on day one.</p>
<p>This release is the beginning of the public journey: source-available core,
developer-first adoption, and a path toward a hosted platform for teams that
want managed operations later.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-loradb-is">What LoraDB Is<a href="https://loradb.com/blog/loradb-public-release#what-loradb-is" class="hash-link" aria-label="Direct link to What LoraDB Is" title="Direct link to What LoraDB Is" translate="no">​</a></h2>
<p>LoraDB is an in-memory property graph database.</p>
<p>It stores:</p>
<ul>
<li class="">nodes with labels and properties;</li>
<li class="">relationships with a type, direction, endpoints, and properties;</li>
<li class="">scalar values, lists, maps, temporal values, and spatial points;</li>
<li class="">query results in row, graph, row-array, and combined formats.</li>
</ul>
<p>It speaks a Cypher-like query language because Cypher is still one of the best
ways to express graph patterns:</p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">MATCH (person:Person)-[:KNOWS]-&gt;(friend:Person)
WHERE person.name = $name
RETURN friend.name, friend.age
ORDER BY friend.age DESC
LIMIT 10</code></pre></div>
<p>Under the hood, LoraDB is split into small Rust crates:</p>
<ul>
<li class=""><code>lora-ast</code> for query syntax structures;</li>
<li class=""><code>lora-parser</code> for parsing;</li>
<li class=""><code>lora-analyzer</code> for semantic analysis;</li>
<li class=""><code>lora-compiler</code> for logical and physical planning;</li>
<li class=""><code>lora-executor</code> for running plans;</li>
<li class=""><code>lora-store</code> for graph storage;</li>
<li class=""><code>lora-database</code> for the database entry point;</li>
<li class=""><code>lora-server</code> for the HTTP server;</li>
<li class=""><code>lora-node</code>, <code>lora-wasm</code>, and <code>lora-python</code> for language bindings.</li>
</ul>
<p>The goal is not to hide the database behind a giant internal system. The goal
is to make the engine readable enough that developers can understand how a
query moves from text to result.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="why-it-exists">Why It Exists<a href="https://loradb.com/blog/loradb-public-release#why-it-exists" class="hash-link" aria-label="Direct link to Why It Exists" title="Direct link to Why It Exists" translate="no">​</a></h2>
<p>LoraDB exists because many graph workloads need to be fast, local, and
efficient before they need to be distributed.</p>
<p>Existing graph databases like Neo4j are powerful, but for the workloads that
started this project, they felt too heavy. I wanted a database that could live
in the application loop, load quickly, run in memory, and make storage costs
easy to reason about.</p>
<p>That means LoraDB is optimized for a specific first experience:</p>
<ol>
<li class="">Clone the repo.</li>
<li class="">Run the server.</li>
<li class="">Load a graph.</li>
<li class="">Write a Cypher query.</li>
<li class="">Understand the result and the code path behind it.</li>
</ol>
<p>The hosted platform will come later. The core has to earn developer trust
first.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-you-can-do-today">What You Can Do Today<a href="https://loradb.com/blog/loradb-public-release#what-you-can-do-today" class="hash-link" aria-label="Direct link to What You Can Do Today" title="Direct link to What You Can Do Today" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="run-the-server">Run the server<a href="https://loradb.com/blog/loradb-public-release#run-the-server" class="hash-link" aria-label="Direct link to Run the server" title="Direct link to Run the server" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-bash codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token plain">cargo run --bin lora-server</span><br></div></code></pre></div></div>
<p>By default, the server listens on <code>127.0.0.1:4747</code>.</p>
<p>Send a query:</p>
<div class="language-bash codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-bash codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token plain">curl -X POST http://127.0.0.1:4747/query \</span><br></div><div class="token-line"><span class="token plain">  -H 'content-type: application/json' \</span><br></div><div class="token-line"><span class="token plain">  -d '{"query":"CREATE (:Person {name: \"Ada\"}) RETURN 1 AS ok"}'</span><br></div></code></pre></div></div>
<p>Check health:</p>
<div class="language-bash codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-bash codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token plain">curl http://127.0.0.1:4747/health</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="use-the-rust-api">Use the Rust API<a href="https://loradb.com/blog/loradb-public-release#use-the-rust-api" class="hash-link" aria-label="Direct link to Use the Rust API" title="Direct link to Use the Rust API" translate="no">​</a></h3>
<p>The Rust API is the most direct way to embed LoraDB in another Rust program:</p>
<div class="language-rust codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-rust codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token keyword">use</span><span class="token plain"> </span><span class="token namespace">lora_database</span><span class="token namespace punctuation">::</span><span class="token class-name">Database</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">let</span><span class="token plain"> db </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">Database</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token plain"></span><br></div><div class="token-line"><span class="token plain"></span><span class="token keyword">let</span><span class="token plain"> rows </span><span class="token operator">=</span><span class="token plain"> db</span><span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span><span class="token string">"MATCH (n) RETURN n LIMIT 10"</span><span class="token punctuation">)</span><span class="token operator">?</span><span class="token punctuation">;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="use-language-bindings">Use language bindings<a href="https://loradb.com/blog/loradb-public-release#use-language-bindings" class="hash-link" aria-label="Direct link to Use language bindings" title="Direct link to Use language bindings" translate="no">​</a></h3>
<p>This repository includes package work for:</p>
<ul>
<li class="">Node.js / TypeScript through <code>crates/bindings/lora-node</code>;</li>
<li class="">WebAssembly through <code>crates/bindings/lora-wasm</code>;</li>
<li class="">Python through <code>crates/bindings/lora-python</code>.</li>
</ul>
<p>The bindings are part of the same public release because the customer journey
should not stop at Rust. Graph workloads show up in web apps, notebooks,
automation tools, agent runtimes, and backend services.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="query-support">Query Support<a href="https://loradb.com/blog/loradb-public-release#query-support" class="hash-link" aria-label="Direct link to Query Support" title="Direct link to Query Support" translate="no">​</a></h2>
<p>This release includes a substantial Cypher-shaped query surface:</p>
<ul>
<li class=""><code>MATCH</code> and <code>OPTIONAL MATCH</code>;</li>
<li class=""><code>WHERE</code>;</li>
<li class=""><code>RETURN</code>;</li>
<li class=""><code>WITH</code>;</li>
<li class=""><code>ORDER BY</code>, <code>SKIP</code>, <code>LIMIT</code>, and <code>DISTINCT</code>;</li>
<li class=""><code>UNWIND</code>;</li>
<li class=""><code>UNION</code> and <code>UNION ALL</code>;</li>
<li class=""><code>CREATE</code>;</li>
<li class=""><code>SET</code>;</li>
<li class=""><code>DELETE</code> and <code>DETACH DELETE</code>;</li>
<li class=""><code>REMOVE</code>;</li>
<li class=""><code>MERGE</code> with <code>ON CREATE</code> and <code>ON MATCH</code>;</li>
<li class="">variable-length paths;</li>
<li class=""><code>shortestPath()</code> and <code>allShortestPaths()</code>;</li>
<li class="">aggregation functions;</li>
<li class="">list, string, math, temporal, spatial, conversion, and entity functions.</li>
</ul>
<p>It also supports parameter binding through the Rust API and typed values for
common graph workloads.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="storage-and-execution">Storage And Execution<a href="https://loradb.com/blog/loradb-public-release#storage-and-execution" class="hash-link" aria-label="Direct link to Storage And Execution" title="Direct link to Storage And Execution" translate="no">​</a></h2>
<p>The storage layer is intentionally in-memory. That is the point of this first
release.</p>
<p>LoraDB is designed around:</p>
<ul>
<li class="">cheap local iteration;</li>
<li class="">predictable graph traversal;</li>
<li class="">explicit query stages;</li>
<li class="">small intermediate representations;</li>
<li class="">clear Rust ownership boundaries;</li>
<li class="">enough structure to evolve toward persistence without hiding current costs.</li>
</ul>
<p>The planner and executor are still young, but they already handle meaningful
graph patterns, projection, filtering, aggregation, updates, and paths. The
project is written so performance work can happen in the open, with the storage
and execution model visible to contributors.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="documentation">Documentation<a href="https://loradb.com/blog/loradb-public-release#documentation" class="hash-link" aria-label="Direct link to Documentation" title="Direct link to Documentation" translate="no">​</a></h2>
<p>The documentation site includes:</p>
<ul>
<li class=""><a class="" href="https://loradb.com/docs/getting-started/installation">getting started guides</a> for every binding;</li>
<li class=""><a class="" href="https://loradb.com/docs/queries">query language pages</a> covering each supported clause;</li>
<li class=""><a class="" href="https://loradb.com/docs/functions/overview">function references</a> — string, math, list, aggregation, temporal, spatial;</li>
<li class=""><a class="" href="https://loradb.com/docs/data-types/overview">data type references</a> — scalars, lists, maps, temporal, spatial, vectors;</li>
<li class=""><a class="" href="https://loradb.com/docs/concepts/graph-model">concept docs</a> for the graph model and schema-free behaviour;</li>
<li class=""><a class="" href="https://loradb.com/docs/cookbook">cookbook recipes</a> by domain;</li>
<li class=""><a class="" href="https://loradb.com/docs/troubleshooting">troubleshooting</a>;</li>
<li class=""><a class="" href="https://loradb.com/docs/limitations">known limitations</a>.</li>
</ul>
<p>The root repository also includes architecture, testing, operations, and
release documentation for contributors who want to understand or improve the
engine.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="license">License<a href="https://loradb.com/blog/loradb-public-release#license" class="hash-link" aria-label="Direct link to License" title="Direct link to License" translate="no">​</a></h2>
<p>The LoraDB core is licensed under the Business Source License 1.1.</p>
<p>The license allows:</p>
<ul>
<li class="">development use;</li>
<li class="">non-production use;</li>
<li class="">internal business use;</li>
<li class="">internal production systems;</li>
<li class="">reading, modifying, and distributing the source under the BSL terms.</li>
</ul>
<p>The license does not allow using the core to offer LoraDB as
database-as-a-service, a hosted API for third parties, a competing managed
database platform, or a hosted resale product.</p>
<p>Each covered release converts to Apache License 2.0 on the Change Date listed
in the root <code>LICENSE</code> file. For this release policy, that date is April 19,
2029.</p>
<p>The documentation website under <code>apps/loradb.com</code> is separately MIT licensed.</p>
<p>The goal is simple: developers should be able to adopt and trust the core,
while the hosted platform business remains sustainable.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-is-not-included-yet">What Is Not Included Yet<a href="https://loradb.com/blog/loradb-public-release#what-is-not-included-yet" class="hash-link" aria-label="Direct link to What Is Not Included Yet" title="Direct link to What Is Not Included Yet" translate="no">​</a></h2>
<p>This release was intentionally honest about what it was not. Current
releases have since added snapshots and WAL-backed local durability;
the rest of this section is the public-launch boundary.</p>
<p>At public launch, LoraDB did not yet include:</p>
<ul>
<li class="">durable disk persistence;</li>
<li class="">WAL or snapshots;</li>
<li class="">clustering;</li>
<li class="">replication;</li>
<li class="">authentication;</li>
<li class="">TLS termination;</li>
<li class="">transactions across concurrent clients;</li>
<li class="">property indexes for every workload;</li>
<li class="">a managed cloud service.</li>
</ul>
<p>The HTTP server is useful for local development, internal experiments, and
controlled environments. Even with today's opt-in snapshot and WAL admin
routes, it is not yet a hardened internet-facing database server.</p>
<p>Those limitations are not hidden. They are the roadmap.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="who-should-try-it">Who Should Try It<a href="https://loradb.com/blog/loradb-public-release#who-should-try-it" class="hash-link" aria-label="Direct link to Who Should Try It" title="Direct link to Who Should Try It" translate="no">​</a></h2>
<p>Start with the <a class="" href="https://loradb.com/docs/getting-started/installation"><strong>installation guide</strong></a>
and the <a class="" href="https://loradb.com/docs/getting-started/tutorial"><strong>ten-minute tour</strong></a> to walk
through the Cypher surface.</p>
<p>Try LoraDB if you are:</p>
<ul>
<li class="">building an internal tool with relationship-heavy data;</li>
<li class="">experimenting with graph memory for agents;</li>
<li class="">prototyping a knowledge graph;</li>
<li class="">looking for a Rust graph database engine you can read;</li>
<li class="">evaluating whether Cypher-shaped queries fit your product;</li>
<li class="">tired of starting with a large graph stack before you know the model works.</li>
</ul>
<p>Do not choose LoraDB yet if you need mature distributed operations, long-term
durability, multi-region replication, or hardened public database hosting
today.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-comes-next">What Comes Next<a href="https://loradb.com/blog/loradb-public-release#what-comes-next" class="hash-link" aria-label="Direct link to What Comes Next" title="Direct link to What Comes Next" translate="no">​</a></h2>
<p>The next phase has three tracks.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="1-core-database-maturity">1. Core database maturity<a href="https://loradb.com/blog/loradb-public-release#1-core-database-maturity" class="hash-link" aria-label="Direct link to 1. Core database maturity" title="Direct link to 1. Core database maturity" translate="no">​</a></h3>
<p>More planner work, better indexing, tighter memory usage, clearer error
messages, and deeper test coverage for Cypher behavior.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="2-persistence">2. Persistence<a href="https://loradb.com/blog/loradb-public-release#2-persistence" class="hash-link" aria-label="Direct link to 2. Persistence" title="Direct link to 2. Persistence" translate="no">​</a></h3>
<p>The in-memory engine is the foundation. The next durable layer should preserve
the simplicity of the current store while adding snapshots, recovery, and a
path toward production workloads that outlive a process.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="3-hosted-platform">3. Hosted platform<a href="https://loradb.com/blog/loradb-public-release#3-hosted-platform" class="hash-link" aria-label="Direct link to 3. Hosted platform" title="Direct link to 3. Hosted platform" translate="no">​</a></h3>
<p>The long-term product is a hosted LoraDB platform for teams that want the graph
model without operating the database themselves. That means managed projects,
backups, metrics, auth, scaling, and support.</p>
<p>The public core and the hosted product are not in conflict. The core creates
developer adoption. The hosted platform turns that adoption into a sustainable
business.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="closing">Closing<a href="https://loradb.com/blog/loradb-public-release#closing" class="hash-link" aria-label="Direct link to Closing" title="Direct link to Closing" translate="no">​</a></h2>
<p>LoraDB started from a practical frustration: I needed a graph database that was
fast enough to live in memory, efficient enough to trust, and small enough to
understand.</p>
<p>This public release is the first serious step toward that goal.</p>
<p>If you try it, the most useful feedback is concrete:</p>
<ul>
<li class="">what graph did you load;</li>
<li class="">which query did you expect to be easy;</li>
<li class="">where did performance surprise you;</li>
<li class="">which limitation blocked you;</li>
<li class="">which docs page did you wish existed.</li>
</ul>
<p>That feedback will shape the next release.</p>
<p>Welcome to LoraDB.</p>]]></content:encoded>
            <category>Release notes</category>
            <category>Announcement</category>
            <category>Architecture</category>
            <category>Cypher</category>
        </item>
        <item>
            <title><![CDATA[From developer trust to hosted platform]]></title>
            <link>https://loradb.com/blog/from-developer-trust-to-hosted-platform</link>
            <guid>https://loradb.com/blog/from-developer-trust-to-hosted-platform</guid>
            <pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The customer journey behind LoraDB: local adoption first, managed operations later.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="From developer trust to hosted platform — local adoption first, managed operations later." src="https://loradb.com/assets/images/from-developer-trust-to-hosted-platform-header-a412a0a4506d599295bce85724864ce9.png" width="1280" height="400" class="img__Ss2"></p>
<p>The easiest way to misunderstand LoraDB is to see the open core and the hosted
platform as separate ideas.</p>
<p>They are the same journey.</p>
<p>The core database has to be developer-first because graph databases ask for a
lot of trust. You are not just storing records. You are putting relationships,
paths, and product logic into a system that needs to be correct and fast. If a
developer cannot run it locally, inspect it, and build confidence in the query
engine, the hosted product has no foundation.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="trust-starts-before-production">Trust Starts Before Production<a href="https://loradb.com/blog/from-developer-trust-to-hosted-platform#trust-starts-before-production" class="hash-link" aria-label="Direct link to Trust Starts Before Production" title="Direct link to Trust Starts Before Production" translate="no">​</a></h2>
<p>The first customer is usually not a procurement team. It is one developer with
a problem that keeps resisting simpler models.</p>
<p>They have a set of entities. The relationships matter. SQL joins are becoming
awkward. A document model hides too much structure. A vector database retrieves
similar things, but it does not explain how they connect.</p>
<p>That developer does not want a sales call first. They want to try the thing.</p>
<p>For LoraDB, the first trust moment should look like this:</p>
<div class="language-bash codeBlockContainer_ZGJx theme-code-block"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-bash codeBlock_TAPP thin-scrollbar"><code class="codeBlockLines_AdAo"><div class="token-line"><span class="token plain">cargo run --bin lora-server</span><br></div></code></pre></div></div>
<p>Then a query:</p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">MATCH (a:Account)-[:OWNS]-&gt;(p:Project)-[:DEPENDS_ON]-&gt;(s:Service)
WHERE a.id = $account
RETURN p.name, collect(s.name) AS services</code></pre></div>
<p>Then a reaction: "This is the shape of my problem."</p>
<p>Everything before that moment is friction.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="why-source-available-core-matters">Why Source-Available Core Matters<a href="https://loradb.com/blog/from-developer-trust-to-hosted-platform#why-source-available-core-matters" class="hash-link" aria-label="Direct link to Why Source-Available Core Matters" title="Direct link to Why Source-Available Core Matters" translate="no">​</a></h2>
<p>For infrastructure, source availability is not only about ideology. It is a
trust tool.</p>
<p>Developers can inspect the parser. They can read the planner. They can see
where values are represented. They can understand limitations without waiting
for marketing language to become documentation.</p>
<p>That is especially important for a young database. LoraDB should not ask
people to believe that every edge case is solved. It should show the work:</p>
<ul>
<li class="">here is the AST;</li>
<li class="">here is semantic analysis;</li>
<li class="">here is logical planning;</li>
<li class="">here is physical execution;</li>
<li class="">here is the in-memory store;</li>
<li class="">here are the tests.</li>
</ul>
<p>That transparency is part of the product.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="why-not-fully-open-for-hosted-resale">Why Not Fully Open For Hosted Resale<a href="https://loradb.com/blog/from-developer-trust-to-hosted-platform#why-not-fully-open-for-hosted-resale" class="hash-link" aria-label="Direct link to Why Not Fully Open For Hosted Resale" title="Direct link to Why Not Fully Open For Hosted Resale" translate="no">​</a></h2>
<p>The other side of the strategy is the BSL license.</p>
<p>The goal is not to stop developers from using LoraDB. The goal is to stop the
core engine from being repackaged immediately as a competing hosted database
service by someone who did not build or maintain it.</p>
<p>That boundary is important because database companies need long time horizons.
The hard work is not only writing a parser or a store. It is maintaining the
engine, supporting users, improving performance, documenting behavior, and
building the operational platform around it.</p>
<p>The BSL lets LoraDB be open enough for adoption while protecting the hosted
business model:</p>
<ul>
<li class="">internal business use is allowed;</li>
<li class="">development and non-production use are allowed;</li>
<li class="">source reading and modification are allowed;</li>
<li class="">hosted database-as-a-service for third parties is restricted;</li>
<li class="">each version converts to Apache 2.0 after the Change Date.</li>
</ul>
<p>That is the intended balance.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-journey-in-four-stages">The Journey In Four Stages<a href="https://loradb.com/blog/from-developer-trust-to-hosted-platform#the-journey-in-four-stages" class="hash-link" aria-label="Direct link to The Journey In Four Stages" title="Direct link to The Journey In Four Stages" translate="no">​</a></h2>
<p>The customer journey I want is deliberately simple.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="1-discovery">1. Discovery<a href="https://loradb.com/blog/from-developer-trust-to-hosted-platform#1-discovery" class="hash-link" aria-label="Direct link to 1. Discovery" title="Direct link to 1. Discovery" translate="no">​</a></h3>
<p>A developer finds LoraDB because they need a graph, not because they want a
platform. They read the docs, run examples, and try the query model.</p>
<p>The goal here is clarity. The website should explain what LoraDB is and what it
is not. The repo should be readable. The license should be explicit.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="2-local-adoption">2. Local Adoption<a href="https://loradb.com/blog/from-developer-trust-to-hosted-platform#2-local-adoption" class="hash-link" aria-label="Direct link to 2. Local Adoption" title="Direct link to 2. Local Adoption" translate="no">​</a></h3>
<p>The developer builds a prototype with local data. They care about:</p>
<ul>
<li class="">quick setup;</li>
<li class="">familiar Cypher-shaped queries;</li>
<li class="">predictable performance;</li>
<li class="">useful errors;</li>
<li class="">enough documentation to keep moving.</li>
</ul>
<p>This is where an in-memory engine shines. No cluster. No hosted account. No
waiting for infrastructure.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="3-internal-production">3. Internal Production<a href="https://loradb.com/blog/from-developer-trust-to-hosted-platform#3-internal-production" class="hash-link" aria-label="Direct link to 3. Internal Production" title="Direct link to 3. Internal Production" translate="no">​</a></h3>
<p>The team starts using LoraDB in an internal tool, agent memory system,
workflow engine, or product feature where the graph is close to the
application.</p>
<p>They care about reliability, performance, and whether limitations are honest.
This is why docs, tests, and release notes matter as much as features.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="4-managed-operations">4. Managed Operations<a href="https://loradb.com/blog/from-developer-trust-to-hosted-platform#4-managed-operations" class="hash-link" aria-label="Direct link to 4. Managed Operations" title="Direct link to 4. Managed Operations" translate="no">​</a></h3>
<p>Eventually, some teams do not want to operate the database themselves. They
need persistence, backups, monitoring, auth, scaling, and support.</p>
<p>That is where the hosted platform belongs. It should not replace developer
adoption. It should follow it.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="why-this-matters-for-product-quality">Why This Matters For Product Quality<a href="https://loradb.com/blog/from-developer-trust-to-hosted-platform#why-this-matters-for-product-quality" class="hash-link" aria-label="Direct link to Why This Matters For Product Quality" title="Direct link to Why This Matters For Product Quality" translate="no">​</a></h2>
<p>A hosted-first database can accidentally optimize for the buyer before the
builder. LoraDB should optimize for the builder first.</p>
<p>That affects product decisions:</p>
<ul>
<li class="">keep the core small enough to understand;</li>
<li class="">make docs practical, not ornamental;</li>
<li class="">publish limitations clearly;</li>
<li class="">make release notes explain why changes matter;</li>
<li class="">keep local development fast;</li>
<li class="">protect the hosted business without making the core feel closed.</li>
</ul>
<p>The hosted platform becomes credible only if the core earns trust.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-i-want-loradb-to-feel-like">What I Want LoraDB To Feel Like<a href="https://loradb.com/blog/from-developer-trust-to-hosted-platform#what-i-want-loradb-to-feel-like" class="hash-link" aria-label="Direct link to What I Want LoraDB To Feel Like" title="Direct link to What I Want LoraDB To Feel Like" translate="no">​</a></h2>
<p>I want LoraDB to feel like a database you can pick up in an afternoon and still
respect after a month.</p>
<p>Small enough to understand. Fast enough to stay close to the application.
Efficient enough that the business model does not depend on waste. Clear enough
that a developer can explain it to a teammate.</p>
<p>That is the bridge from developer trust to hosted platform.</p>
<p>The final post in this series is the public release announcement: what LoraDB
is today, what is included, what is intentionally not included yet, and where
the project goes next.</p>]]></content:encoded>
            <category>Founder notes</category>
            <category>Design</category>
            <category>Architecture</category>
        </item>
        <item>
            <title><![CDATA[Efficient storage is the product]]></title>
            <link>https://loradb.com/blog/efficient-storage-is-the-product</link>
            <guid>https://loradb.com/blog/efficient-storage-is-the-product</guid>
            <pubDate>Sun, 12 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Why LoraDB treats memory layout, traversal cost, and predictable data structures as product features.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Efficient storage is the product — memory layout and traversal cost are product features." src="https://loradb.com/assets/images/efficient-storage-is-the-product-header-12c2d34fa3914974b62e9b7701161386.png" width="1280" height="400" class="img__Ss2"></p>
<p>When people talk about graph databases, they usually talk about query
languages, visualizations, and relationship modeling. All of that matters. But
for the kind of database I wanted, the deeper product question was storage.</p>
<p>If the database is in memory, storage efficiency is not an implementation
detail. It is the product boundary.</p>
<p>Every extra allocation is less graph. Every unnecessary clone is less fan-out.
Every vague data structure is a future performance mystery. A graph database
can have a beautiful query language and still feel wrong if the storage layer
wastes the machine.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-real-cost-of-a-graph">The Real Cost Of A Graph<a href="https://loradb.com/blog/efficient-storage-is-the-product#the-real-cost-of-a-graph" class="hash-link" aria-label="Direct link to The Real Cost Of A Graph" title="Direct link to The Real Cost Of A Graph" translate="no">​</a></h2>
<p>Graphs are expensive in a specific way.</p>
<p>A node is not just a row. It has labels, properties, and identity. A
relationship is not just a foreign key. It has direction, type, endpoints, and
often properties of its own. A traversal needs to find the next relationships
quickly, then carry enough state to avoid incorrect paths, cycles, or duplicate
rows.</p>
<p>That means the storage layer needs to answer several questions cheaply:</p>
<ul>
<li class="">Which nodes have this label?</li>
<li class="">Which relationships leave this node?</li>
<li class="">Which relationships enter this node?</li>
<li class="">Which relationships have this type?</li>
<li class="">What properties are needed for this query?</li>
<li class="">Can the executor avoid materializing more than it returns?</li>
</ul>
<p>If the answer to all of those questions is "scan and allocate," the database
may still be simple, but it will not be efficient.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-bothered-me-about-existing-options">What Bothered Me About Existing Options<a href="https://loradb.com/blog/efficient-storage-is-the-product#what-bothered-me-about-existing-options" class="hash-link" aria-label="Direct link to What Bothered Me About Existing Options" title="Direct link to What Bothered Me About Existing Options" translate="no">​</a></h2>
<p>This is where my frustration with existing graph databases became concrete.</p>
<p>I liked the graph model. I liked Cypher. I liked being able to express a path
in a way that looked like the domain. What I did not like was the cost profile
around many systems: too much memory overhead for small graphs, too much
operational weight for local workloads, too much indirection when the graph was
supposed to be close to the application.</p>
<p>Many graph databases are built for broad, durable, server-side
workloads. That comes with real strengths. It also means they carry machinery
that a fast embedded or in-process graph engine may not need.</p>
<p>LoraDB starts from a different question:</p>
<blockquote>
<p>If the working set is in memory, what is the smallest honest storage model
that can support expressive graph queries?</p>
</blockquote>
<p>That question shaped the project more than any single feature.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="storage-has-to-match-the-query-engine">Storage Has To Match The Query Engine<a href="https://loradb.com/blog/efficient-storage-is-the-product#storage-has-to-match-the-query-engine" class="hash-link" aria-label="Direct link to Storage Has To Match The Query Engine" title="Direct link to Storage Has To Match The Query Engine" translate="no">​</a></h2>
<p>The easiest way to build a database is to keep storage generic and make the
executor compensate. The executor can scan, filter, clone, sort, and project
until the result is correct.</p>
<p>Correct is not enough.</p>
<p>The storage layer and query engine have to meet in the middle. If a query only
needs <code>n.name</code>, storage should not force the executor to materialize the whole
node. If a pattern expands out from a known node, the executor should be able
to ask for outgoing relationships directly. If a label narrows the candidate
set, the planner should be able to use that fact.</p>
<p>That is why LoraDB is split into small crates. Storage, analyzer, compiler, and
executor each have a narrow job, but the boundaries are designed so efficiency
can move through the pipeline.</p>
<p>The planner can preserve useful shape. The executor can request the values it
needs. The store can provide graph-oriented access paths instead of pretending
everything is a table.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="memory-is-a-budget-not-a-pool">Memory Is A Budget, Not A Pool<a href="https://loradb.com/blog/efficient-storage-is-the-product#memory-is-a-budget-not-a-pool" class="hash-link" aria-label="Direct link to Memory Is A Budget, Not A Pool" title="Direct link to Memory Is A Budget, Not A Pool" translate="no">​</a></h2>
<p>An in-memory database makes memory visible. That is good.</p>
<p>Disk-first systems can sometimes hide inefficient intermediate structures
behind page caches, background work, or larger machines. In-memory systems do
not get that luxury. If a query creates too many rows or keeps too many values
alive, you feel it quickly.</p>
<p>That pressure leads to better engineering:</p>
<ul>
<li class="">prefer stable identifiers over copying whole entities;</li>
<li class="">project only what the query asks for;</li>
<li class="">keep intermediate rows narrow;</li>
<li class="">make path expansion explicit;</li>
<li class="">choose data structures whose cost can be explained;</li>
<li class="">avoid turning every operation into a serialization boundary.</li>
</ul>
<p>The result is not just better performance. It is better developer experience,
because the system becomes easier to reason about.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="efficient-storage-changes-the-customer-journey">Efficient Storage Changes The Customer Journey<a href="https://loradb.com/blog/efficient-storage-is-the-product#efficient-storage-changes-the-customer-journey" class="hash-link" aria-label="Direct link to Efficient Storage Changes The Customer Journey" title="Direct link to Efficient Storage Changes The Customer Journey" translate="no">​</a></h2>
<p>Storage efficiency is not only about serving large users. It helps the first
user too.</p>
<p>A developer evaluating LoraDB should be able to start with a laptop and a real
slice of data. They should not need to provision an oversized machine because
the graph representation is bloated. They should not need to design a caching
strategy before writing the first useful query.</p>
<p>Efficient storage makes the journey smoother:</p>
<ol>
<li class="">The local prototype feels fast.</li>
<li class="">The first internal workload stays cheap.</li>
<li class="">The team trusts that performance comes from the engine, not from accident.</li>
<li class="">The hosted platform later has a strong unit economics base.</li>
</ol>
<p>That last point matters. A hosted database company lives or dies on efficiency.
If the core engine wastes memory, the cloud product either becomes expensive or
slow. LoraDB's business strategy depends on the same thing the developer
experience depends on: doing more with less.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-efficient-means-for-loradb">What "Efficient" Means For LoraDB<a href="https://loradb.com/blog/efficient-storage-is-the-product#what-efficient-means-for-loradb" class="hash-link" aria-label="Direct link to What &quot;Efficient&quot; Means For LoraDB" title="Direct link to What &quot;Efficient&quot; Means For LoraDB" translate="no">​</a></h2>
<p>For LoraDB, efficient storage means:</p>
<ul>
<li class="">graph-native access to nodes and relationships;</li>
<li class="">predictable in-memory structures;</li>
<li class="">cheap directional traversal;</li>
<li class="">clear ownership and borrowing in Rust;</li>
<li class="">minimal materialization between query stages;</li>
<li class="">APIs that can evolve toward persistence without hiding current costs.</li>
</ul>
<p>It does not mean the first version is finished. It means the system is built
around the right constraint.</p>
<p>I would rather have a smaller database with a storage model I can explain than
a larger database that only feels fast in benchmark slides.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-standard-i-want">The Standard I Want<a href="https://loradb.com/blog/efficient-storage-is-the-product#the-standard-i-want" class="hash-link" aria-label="Direct link to The Standard I Want" title="Direct link to The Standard I Want" translate="no">​</a></h2>
<p>The standard for LoraDB is simple:</p>
<p>When a developer asks why a query used the memory it used, we should be able to
answer.</p>
<p>When a customer asks why hosted LoraDB can be cost-effective, the answer should
start in the storage engine, not in pricing tricks.</p>
<p>When a contributor opens the code, the core data structures should be legible.</p>
<p>That is why efficient storage is not a hidden concern. It is the product.</p>
<p>The next post is about trust: how a developer-first graph database becomes a
real customer journey instead of just a repository with good intentions.</p>]]></content:encoded>
            <category>Founder notes</category>
            <category>Architecture</category>
            <category>Deep dive</category>
        </item>
        <item>
            <title><![CDATA[In-memory or it does not work]]></title>
            <link>https://loradb.com/blog/in-memory-or-it-does-not-work</link>
            <guid>https://loradb.com/blog/in-memory-or-it-does-not-work</guid>
            <pubDate>Wed, 08 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Why LoraDB starts with hot data, predictable traversal, and a graph engine close to the application.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="In-memory or it does not work — hot data, predictable traversal, the engine close to the application." src="https://loradb.com/assets/images/in-memory-or-it-does-not-work-header-14522bc3a90d3da03c50b59710385b07.png" width="1280" height="400" class="img__Ss2"></p>
<p>The phrase "in-memory database" can sound like a performance trick. For
LoraDB, it is more basic than that. The product I wanted to build did not make
sense if the graph was slow to touch.</p>
<p>Graphs are not like simple key-value lookups. The interesting queries walk.
They expand. They branch. They filter while moving through relationships. A
single product interaction can turn into a set of small traversals that need to
feel instant.</p>
<p>If the graph is on the hot path, latency is not an optimization. It is the
product.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-moment-a-graph-becomes-product-logic">The Moment A Graph Becomes Product Logic<a href="https://loradb.com/blog/in-memory-or-it-does-not-work#the-moment-a-graph-becomes-product-logic" class="hash-link" aria-label="Direct link to The Moment A Graph Becomes Product Logic" title="Direct link to The Moment A Graph Becomes Product Logic" translate="no">​</a></h2>
<p>The first version of a graph workload is often a background report:</p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">MATCH (a)-[:CONNECTED_TO*1..3]-&gt;(b)
RETURN a, b
LIMIT 100</code></pre></div>
<p>That can be slow and still useful. Someone waits, gets an answer, and learns
something.</p>
<p>But the workloads I cared about moved closer to the user:</p>
<ul>
<li class="">Which related entities should appear while someone is typing?</li>
<li class="">Which memories should an agent read before choosing a tool?</li>
<li class="">Which product, document, or event is connected enough to matter now?</li>
<li class="">Which path explains why two things are related?</li>
</ul>
<p>Those are not nightly jobs. Those are interaction-time questions.</p>
<p>Once a graph query becomes product logic, a few milliseconds change the shape
of what you can build. You stop treating the graph as a separate analytics
system and start treating it as part of the application runtime.</p>
<p>That is where in-memory starts to matter.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="why-a-remote-database-often-felt-wrong">Why A Remote Database Often Felt Wrong<a href="https://loradb.com/blog/in-memory-or-it-does-not-work#why-a-remote-database-often-felt-wrong" class="hash-link" aria-label="Direct link to Why A Remote Database Often Felt Wrong" title="Direct link to Why A Remote Database Often Felt Wrong" translate="no">​</a></h2>
<p>Remote databases are powerful. They centralize state. They add durability,
access control, replication, backup, and operational boundaries.</p>
<p>They also add distance.</p>
<p>For a graph workload, that distance can be painful because the query engine is
already doing pointer-heavy work. Relationship expansion is not one lookup; it
is a sequence of dependent reads. If the system has to cross process, network,
serialization, and storage boundaries for every meaningful step, the database
may still be correct, but the product no longer feels direct.</p>
<p>With LoraDB, I wanted the first experience to be different:</p>
<ul>
<li class="">the graph lives next to the code;</li>
<li class="">the query engine runs in the same machine;</li>
<li class="">the storage layout is optimized for traversal;</li>
<li class="">the cost of experimenting is low;</li>
<li class="">tests can spin up a database without infrastructure.</li>
</ul>
<p>That is not the only valid architecture. It is the right first architecture for
the customer journey I wanted.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="fast-is-not-just-benchmarks">Fast Is Not Just Benchmarks<a href="https://loradb.com/blog/in-memory-or-it-does-not-work#fast-is-not-just-benchmarks" class="hash-link" aria-label="Direct link to Fast Is Not Just Benchmarks" title="Direct link to Fast Is Not Just Benchmarks" translate="no">​</a></h2>
<p>I care about benchmarks, but benchmarks are not the whole story.</p>
<p>For a developer trying a database, "fast" means:</p>
<ul>
<li class="">the server starts quickly;</li>
<li class="">small graphs load quickly;</li>
<li class="">common queries complete without surprise;</li>
<li class="">the API does not force unnecessary data conversion;</li>
<li class="">the result shape is predictable;</li>
<li class="">the failure mode is understandable.</li>
</ul>
<p>A graph database can post impressive numbers and still feel slow if every
interaction requires operational setup. It can also feel unreliable if a query
allocates aggressively, materializes too much, or hides planner choices.</p>
<p>LoraDB's first design goal was to make the happy path cheap:</p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">MATCH (u:User)-[:LIKES]-&gt;(topic:Topic)
WHERE u.id = $user_id
RETURN topic.name
ORDER BY topic.score DESC
LIMIT 10</code></pre></div>
<p>That kind of query should not feel like an analytics job. It should feel like
ordinary application code.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="why-cypher-still-matters">Why Cypher Still Matters<a href="https://loradb.com/blog/in-memory-or-it-does-not-work#why-cypher-still-matters" class="hash-link" aria-label="Direct link to Why Cypher Still Matters" title="Direct link to Why Cypher Still Matters" translate="no">​</a></h2>
<p>If speed were the only goal, the easiest answer would be a custom Rust API with
hand-written traversal functions. That would be faster to implement and easier
to optimize in the narrow case.</p>
<p>But it would make the customer journey worse.</p>
<p>Cypher gives the database a shared language. A developer can look at a query
and understand the shape of the graph. A teammate can review it. A user moving
from another graph system does not have to learn an entirely new model before
getting value.</p>
<p>The tradeoff is implementation cost. A real query language means parser,
semantic analysis, planning, execution, projection, aggregation, paths,
functions, errors, and documentation.</p>
<p>I accepted that cost because the goal was not just "fast graph operations." The
goal was a database.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="why-in-memory-does-not-mean-toy">Why In-Memory Does Not Mean Toy<a href="https://loradb.com/blog/in-memory-or-it-does-not-work#why-in-memory-does-not-mean-toy" class="hash-link" aria-label="Direct link to Why In-Memory Does Not Mean Toy" title="Direct link to Why In-Memory Does Not Mean Toy" translate="no">​</a></h2>
<p>There is a common assumption that in-memory means temporary, toy, or
non-serious. I think that assumption comes from confusing deployment model with
engineering quality.</p>
<p>An in-memory graph database can still have:</p>
<ul>
<li class="">a real query language;</li>
<li class="">a real planner;</li>
<li class="">typed values;</li>
<li class="">path semantics;</li>
<li class="">deterministic tests;</li>
<li class="">production use for internal workloads;</li>
<li class="">clear boundaries for future persistence.</li>
</ul>
<p>Starting in memory makes the first version more honest. It forces the core
engine to be useful before the system grows persistence, clustering, and
managed operations around it.</p>
<p>For LoraDB, that was important. I did not want to hide inefficiency behind
hardware. I wanted the engine to be small enough that performance problems had
nowhere to hide.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-product-feeling">The Product Feeling<a href="https://loradb.com/blog/in-memory-or-it-does-not-work#the-product-feeling" class="hash-link" aria-label="Direct link to The Product Feeling" title="Direct link to The Product Feeling" translate="no">​</a></h2>
<p>The product feeling I wanted is this:</p>
<p>You clone the repo. You run the server. You send a Cypher query. You get a
result. You can read the code path from HTTP request to query plan to graph
storage without losing the thread.</p>
<p>That feeling builds trust.</p>
<p>Trust is what turns a curious developer into an adopter. Adoption is what makes
a hosted platform possible later. The hosted product can add persistence,
backups, scaling, dashboards, auth, and operational guarantees, but the core
has to feel right first.</p>
<p>That is why LoraDB begins in memory.</p>
<p>The next post is about the second constraint: storage efficiency. Because a
fast in-memory graph database is only useful if it uses memory carefully.</p>]]></content:encoded>
            <category>Founder notes</category>
            <category>Architecture</category>
            <category>Design</category>
        </item>
        <item>
            <title><![CDATA[Why I started LoraDB]]></title>
            <link>https://loradb.com/blog/why-i-started-loradb</link>
            <guid>https://loradb.com/blog/why-i-started-loradb</guid>
            <pubDate>Sun, 05 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The first reason was simple: I needed a graph database that felt fast enough to keep in the hot path.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Why I started LoraDB — a graph database fast enough to keep in the hot path." src="https://loradb.com/assets/images/why-i-started-loradb-header-3868ac4fec93a0af379017cd79a7fd5a.png" width="1280" height="400" class="img__Ss2"></p>
<p>I did not start LoraDB because the world was missing another database with a
logo and a query language. I started it because I kept reaching for a graph
database in places where the existing choices felt too heavy for the job.</p>
<p>The shape of the problem was clear: I needed a really fast in-memory graph
database. Not a graph feature bolted onto a document store. Not a large server
that needed its own operational plan before I could answer a product question.
Not a database that looked elegant in a demo but became expensive once the
working set, query fan-out, and deployment model got real.</p>
<p>I needed something smaller, sharper, and more efficient.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-workload-that-kept-coming-back">The Workload That Kept Coming Back<a href="https://loradb.com/blog/why-i-started-loradb#the-workload-that-kept-coming-back" class="hash-link" aria-label="Direct link to The Workload That Kept Coming Back" title="Direct link to The Workload That Kept Coming Back" translate="no">​</a></h2>
<p>The same workload showed up in different clothes:</p>
<ul>
<li class="">entities connected by typed relationships;</li>
<li class="">metadata that mattered at query time;</li>
<li class="">short multi-hop traversals;</li>
<li class="">ranking, filtering, and projection over neighborhoods;</li>
<li class="">state that should be close to the application, not across a slow boundary.</li>
</ul>
<p>Sometimes the domain was product data. Sometimes it was memory for agents.
Sometimes it was internal tooling. The model kept becoming a graph because the
real world kept refusing to be a table.</p>
<p>The annoying part was not the graph model. The graph model was the relief. The
annoying part was the machinery around it.</p>
<p>I wanted to ask questions like:</p>
<div class="root_k0KZ"><pre class="fallback_FbSR language-cypher"><code class="language-cypher">MATCH (u:User)-[:WORKED_ON]-&gt;(p:Project)-[:USES]-&gt;(t:Technology)
WHERE u.id = $user_id
RETURN t.name, count(*) AS weight
ORDER BY weight DESC
LIMIT 20</code></pre></div>
<p>And I wanted that query to be cheap enough that I did not have to build a cache
around the database on day one.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="respect-for-existing-systems-frustration-with-the-fit">Respect For Existing Systems, Frustration With The Fit<a href="https://loradb.com/blog/why-i-started-loradb#respect-for-existing-systems-frustration-with-the-fit" class="hash-link" aria-label="Direct link to Respect For Existing Systems, Frustration With The Fit" title="Direct link to Respect For Existing Systems, Frustration With The Fit" translate="no">​</a></h2>
<p>Neo4j did a lot for the graph database category. It made Cypher feel normal.
It made graph queries approachable. It gave developers a mental model that is
still useful.</p>
<p>But for the systems I wanted to build, I did not like the efficiency tradeoff.
The operational footprint was bigger than the product surface I needed. The
runtime model was not shaped around embedding. The storage model was not
something I could easily reason about from inside a small application. Other
graph databases had their own strengths, but I kept seeing the same tension:
great ideas wrapped in systems that were too large, too remote, or too costly
for the hot path.</p>
<p>That does not make those systems bad. It means they were solving a broader
problem than mine.</p>
<p>My problem was narrower:</p>
<blockquote>
<p>Give me a graph database I can understand, run locally, keep in memory, query
with a familiar language, and make efficient enough that I trust it in the
product loop.</p>
</blockquote>
<p>LoraDB started there.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="why-in-memory-first">Why In-Memory First<a href="https://loradb.com/blog/why-i-started-loradb#why-in-memory-first" class="hash-link" aria-label="Direct link to Why In-Memory First" title="Direct link to Why In-Memory First" translate="no">​</a></h2>
<p>In-memory is not a shortcut. It is a product decision.</p>
<p>A database that begins in memory can optimize for a different feeling:</p>
<ul>
<li class="">startup should be quick;</li>
<li class="">tests should be cheap;</li>
<li class="">queries should be predictable;</li>
<li class="">the storage layout should be easy to inspect;</li>
<li class="">the developer should not need a cluster before they have a prototype.</li>
</ul>
<p>For the first version of LoraDB, persistence was less important than making the
core query engine honest. If the parser, analyzer, planner, executor, and graph
store are clean in memory, then durability can be added from a strong base. If
the core is slow or unclear, persistence only preserves the wrong shape.</p>
<p>The first principle was speed in the loop. Can you model a graph, load it, run
queries, and understand what happened without ceremony?</p>
<p>That is what I wanted as a developer. That is also what I think creates the
right customer journey: adopt locally, trust the engine, then choose hosted
operations when the product deserves it.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-customer-journey-i-wanted">The Customer Journey I Wanted<a href="https://loradb.com/blog/why-i-started-loradb#the-customer-journey-i-wanted" class="hash-link" aria-label="Direct link to The Customer Journey I Wanted" title="Direct link to The Customer Journey I Wanted" translate="no">​</a></h2>
<p>Most infrastructure products ask for trust too early.</p>
<p>They ask you to sign up, provision, configure, connect, migrate, monitor, and
only then discover whether the data model even fits your problem. For a graph
database, that is backwards. Developers need to feel the model first.</p>
<p>The journey should be:</p>
<ol>
<li class="">Run LoraDB locally.</li>
<li class="">Load a small graph.</li>
<li class="">Write a Cypher query that feels like the problem.</li>
<li class="">See that it is fast enough to stay in the application path.</li>
<li class="">Build something real.</li>
<li class="">Move to managed infrastructure when operations become the expensive part.</li>
</ol>
<p>That is the company model behind LoraDB: developer-first core, hosted platform
later. The database has to earn adoption before the platform can earn revenue.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="what-i-refused-to-hide">What I Refused To Hide<a href="https://loradb.com/blog/why-i-started-loradb#what-i-refused-to-hide" class="hash-link" aria-label="Direct link to What I Refused To Hide" title="Direct link to What I Refused To Hide" translate="no">​</a></h2>
<p>The first versions of LoraDB are intentionally explicit about limitations. It
is in-memory. It does not pretend to be a distributed system. It does not hide
missing pieces behind enterprise language. It is a graph database engine with a
clear query pipeline and a storage model that can be read.</p>
<p>That matters because efficiency is not only a benchmark result. Efficiency is
also whether a developer can answer:</p>
<ul>
<li class="">Where did this allocation come from?</li>
<li class="">Why did the planner choose this direction?</li>
<li class="">How many rows does this operation materialize?</li>
<li class="">What does this value look like in storage?</li>
<li class="">Can I debug this without becoming an archaeologist?</li>
</ul>
<p>I started LoraDB because I wanted those answers to be reachable.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_Iwt7" id="the-bet">The Bet<a href="https://loradb.com/blog/why-i-started-loradb#the-bet" class="hash-link" aria-label="Direct link to The Bet" title="Direct link to The Bet" translate="no">​</a></h2>
<p>The bet is that there is room for a graph database that is smaller, faster to
adopt, easier to embed, and honest about the path from open development to a
hosted business.</p>
<p>Not every workload needs LoraDB. Some need a mature cluster with years of
operational tooling. Some need disk-first durability from the first write. Some
need distributed graph processing.</p>
<p>But many teams need a fast graph engine close to the application. Many agent
systems need structured memory. Many products need relationship queries before
they need a database department.</p>
<p>That is why I started LoraDB.</p>
<p>The next post is about the first technical constraint that shaped everything:
the database had to be fast enough to live in memory without becoming wasteful.</p>]]></content:encoded>
            <category>Founder notes</category>
            <category>Design</category>
            <category>Announcement</category>
        </item>
    </channel>
</rss>