TIMETABLE & REGULATIONS
Documentation
Everything needed to run SpeedyDb in the browser: quick start, the full API surface, best practices, pricing, and licensing.
Quick start
SpeedyDb ships as a WebAssembly bundle (pkg/) plus two host
modules: durable.js (IndexedDB persistence) and
embedder.js (the on-device embedding model). Copy those four
pieces into your app and serve them from the same origin — npm packaging is
on the roadmap.
import initSpeedyDb from "./pkg/speedydb.js";
import { openDurable } from "./durable.js";
import { embedQuery, embedDocuments, EMBED_MODEL_ID, EMBED_DIM } from "./embedder.js";
// 1 · boot the engine, open (or restore) a persistent database
const mod = await import("./pkg/speedydb.js");
await mod.default();
const handle = await openDurable(mod, { dbName: "my-app" });
const db = handle.db;
// 2 · ingest — keyed sources are idempotent (same key ⇒ same rows back)
const rows = db.putSourceTextKeyed("doc:guide", ["chunk one", "chunk two"]);
handle.scheduleFlush(); // write-behind to IndexedDB
// 3 · exact search, context expansion
db.findText("chunk", 5);
db.expandText(rows[0], 1n); // i64 params are BigInt
// 4 · semantic search (downloads the model once, ~229 MB, cached)
db.ensureEmbeddingModel(EMBED_MODEL_ID, EMBED_DIM);
db.putEmbeddings(Array.from(rows, Number),
await embedDocuments(["chunk one", "chunk two"]));
const hits = db.findSemantic(await embedQuery("what is this about?"), 5);
await handle.close(); // final flush + lock release
Native (Rust) usage opens the same byte-identical .spdb files
through the speedydb crate — a browser export imports natively
and vice versa.
API reference
The complete JavaScript surface. i64 parameters are BigInt; hit objects carry row ids as plain Numbers.
Database lifecycle
| endpoint | what it does |
|---|---|
| openDurable(mod, {dbName, segmentLen?, contentCapacity?}) | Open (or restore) a database persisted in IndexedDB. Returns {db, flush, scheduleFlush, close, reset}. Holds a Web Lock — a second tab fails cleanly (error has .speedydbLocked). |
| new WasmBrowserDb(backend, segmentLen, contentCapacity) | Ephemeral in-memory database ("memory" or "segmented" backend) — resets on reload. Good for scratch work and tests. |
| db.reopen() | Close (persisting state) and reopen over the same backing store. Consumes the handle; use the returned one. |
| db.flush() | Flush pending engine writes (durability to IndexedDB is the host's flush — see Persistence). |
| handle.reset() / destroyDurable(dbName) | Delete the persisted database entirely. |
Ingest
| endpoint | what it does |
|---|---|
| db.putSourceTextKeyed(key, chunks) | Idempotent ingest under a stable key (e.g. a content hash). Returns the rows' ids — existing rows if the key is already active. |
| db.putSourceText(chunks) | Unkeyed ingest; every call creates a new source. |
| db.deactivateSource(key) / db.deactivateRow(id) | Soft delete — rows flip inactive, are excluded from search, and never physically removed. |
Fetch & context
| endpoint | what it does |
|---|---|
| db.getText(id) / db.getBytes(id) | One row's payload (undefined if unknown). |
| db.isActive(id) | true/false/undefined (unknown id). |
| db.expandText(id, n) | The row plus up to n neighbours each side within its source, clamped at source edges. |
| db.rowCount() / db.sourceCount() | Totals (row ids are dense, 1-based, never reused). |
Search
| endpoint | what it does |
|---|---|
| db.findText(query, limit) | Case-insensitive substring scan over active rows (limit ≤ 50). O(rows) — a convenience scan, not an index. |
| db.findSemantic(queryVec, limit) | Cosine top-k over active rows with stored vectors. Same hit shape as findText plus score. |
Files & sync
| endpoint | what it does |
|---|---|
| db.listFiles() / db.exportFile(key) / db.importFile(key, bytes) | Byte-exact .spdb snapshot surface — exports open natively, native files import here. Call reopen() after imports. |
| db.isDurable() · db.takeDirty() · db.exportSegment(path, i) · db.exportLen(path) | The write-behind persistence protocol (drained by durable.js; you only need these to build a custom persistence host). |
| db.linkFromRedeem(json, deviceId, at) · db.linkStatus() · db.unlinkDevice() | Master-link handoff: persist/read/remove this database's link descriptor (no secrets stored). |
Semantic search
SpeedyDb ships with the approved on-device embedding model —
LiquidAI/LFM2.5-Embedding-350M (1024-dim, CLS pooling), running on
the vendored llama.cpp WebAssembly runtime. Weights (official GGUF, ~229 MB)
download on first use and are cached by the browser.
query: prefix and passages with
document: — omitting them silently degrades retrieval.
embedQuery() / embedDocuments() apply them for you;
never embed raw strings another way.
| endpoint | what it does |
|---|---|
| loadEmbedder(onProgress?) | Load the model (idempotent; reports download progress). |
| embedQuery(text) / embedDocuments(texts, onEach?) | L2-normalized vectors with the mandatory prefixes applied. |
| db.ensureEmbeddingModel(modelId, dim) | Pin the embedding space. Returns false when created fresh or a different space was wiped — mixing spaces corrupts retrieval, so a mismatch always clears. Re-embed after false. |
| db.putEmbeddings(rowIds, flatF32) | Store one vector per row (row-major Float32Array). Vectors persist and restore with the database. |
| db.rowsMissingEmbeddings(limit) | Active rows without vectors — the backfill worklist. |
| db.embeddingModel() | The stored space as {"model_id","dim"} JSON, or undefined. |
Persistence model
The engine runs synchronously over in-memory segments; IndexedDB is the
durability layer. On open, durable.js hydrates every persisted
record; after mutations it drains the engine's dirty record keys and writes
only those back in one transaction (write-behind). Everything — content rows,
sidecars, semantic vectors — rides the same mechanism and survives reloads.
- Durability is advisory: a flush scheduled but not committed when the page dies is lost; that ingest simply re-runs next visit.
- Single tab: a Web Lock makes a second tab's open fail cleanly rather than corrupt via last-flush-wins.
- Byte compatibility: exported files are byte-identical to native
.spdb— a browser export opens on the server and vice versa.
Best practices
- Key your sources. Use a content hash or stable document id with
putSourceTextKeyedso re-ingesting is a no-op instead of a duplicate. - Chunk to fit. Keep each chunk within
contentCapacitybytes (set at creation). Split on paragraph boundaries; group CSV lines under a repeated header. - Flush after writes. Call
scheduleFlush()after mutations andflush()onpagehide; callclose()when done. - One embedding space. Same model, dim, and ideally the same quantization everywhere vectors are compared. Treat
ensureEmbeddingModel(...) === falseas "re-embed now". - Backfill in batches. Drain
rowsMissingEmbeddingsin bounded batches so the UI stays responsive during large backfills. - Prefer findSemantic at scale.
findTextis an O(rows) scan for small sets and debugging; semantic search is the retrieval path. - Soft-delete, don't rewrite. Deactivate sources/rows; ids are never reused, so references stay valid.
Pricing
The in-browser demo on this site is free to try. SpeedyDb itself is commercially licensed software; production pricing (per-seat, embedded/OEM, and master-server tiers) is being finalized.
Licensing
SpeedyDb is proprietary software — © 2026 Griffin Pilz, all
rights reserved. It is not open source and not free software: no use, copying,
modification, distribution, or reverse engineering is permitted without a
separate signed written agreement, and the software is provided "as is"
without warranty. The full text ships as LICENSE with the
software.
Third-party components
| component | license · role |
|---|---|
| wllama / llama.cpp | MIT — vendored WebAssembly inference runtime for the embedding model. |
| LiquidAI LFM2.5-Embedding-350M | LFM Open License — model weights, fetched from the official repository at runtime (not redistributed here). |
| wasm-bindgen / js-sys / serde | MIT/Apache-2.0 — compiled into the WebAssembly bundle. |