fix: use in-memory HNSW with auto-rebuild & add i18n.js web server route#49
Conversation
hnswlib-wasm requires browser environment (Emscripten -sENVIRONMENT=web) and does not export FS for real filesystem bridging. This commit: - Adds globalThis.window monkey-patch for Node.js/Bun compatibility - Uses correct hnswlib-wasm API: initIndex(maxElements, m, efConstruction, seed), addPoint(vector, label, replaceDeleted), searchKnn(vector, k, filter) - Keeps HNSW indexes purely in-memory (no file persistence) - Auto-rebuilds indexes from SQLite vectors on first search after restart - Adds isPopulated() method to HNSWIndex for rebuild detection - Removes unused readFileSync import and getVirtualFilename helper
hnswlib-wasm's autoSave feature triggers IDBFS sync on every addPoint and markDelete call. Since we use in-memory-only indexes (no file persistence), pass empty string to disable auto-save and reduce stderr noise from missing indexedDB in Node.js/Bun environment.
There was a problem hiding this comment.
Pull request overview
This follow-up to PR #48 fixes two runtime-discovered issues: (1) hnswlib-wasm's Emscripten browser-only compilation preventing HNSW from working in Node.js/Bun, solved by switching to purely in-memory indexes rebuilt on-demand from SQLite; and (2) a missing /i18n.js static route in the web server causing the web UI to fail to load.
Changes:
hnsw-index.ts: Adds aglobalThis.windowpolyfill to load the browser-only WASM library in Node.js/Bun, switches HNSW to in-memory-only with correct API arguments, persists only.metaid-mapping files, and addsisPopulated().vector-search.ts: Adds auto-rebuild of the in-memory HNSW index from SQLite when a search is triggered on an empty index (i.e., after process restart).web-server.ts: Adds a static file route for/i18n.js.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/services/sqlite/hnsw-index.ts |
In-memory HNSW with window polyfill, correct API usage, meta-only persistence |
src/services/sqlite/vector-search.ts |
Auto-rebuild HNSW index from SQLite on first search after restart |
src/services/web-server.ts |
Add /i18n.js static route to fix web UI loading |
Comments suppressed due to low confidence (1)
src/services/sqlite/hnsw-index.ts:152
- The
.metafile (containingnextId,idMap,reverseMap) is still written to disk on everyinsert(),insertBatch(), anddelete()call viasave(), but is never read back anywhere in the codebase —readFileSynchas been removed entirely. On process restart, the HNSW index and its id mappings are fully rebuilt from SQLite viarebuildFromShard(), which re-assigns fresh internal IDs. Writing the meta file is now dead I/O on every mutation. Thesave()method and all related filesystem code (writeFileSync, thedirexistence check, themetaPathconstruction) should be removed to avoid unnecessary disk writes on every insert/delete.
async save(): Promise<void> {
if (!this.index) return;
const dir = dirname(this.indexPath);
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
// Only persist id mapping (.meta file). HNSW index data lives in-memory
// and is rebuilt from SQLite vectors on process restart.
const metaPath = this.indexPath + ".meta";
const meta = {
nextId: this.nextId,
idMap: Object.fromEntries(this.idMap),
reverseMap: Object.fromEntries(this.reverseMap),
};
writeFileSync(metaPath, JSON.stringify(meta));
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (path === "/i18n.js") { | ||
| return this.serveStaticFile("i18n.js", "application/javascript"); | ||
| } |
There was a problem hiding this comment.
The /i18n.js route is added to web-server.ts, but an identical static file routing block exists in web-server-worker.ts (the Bun worker variant of the server). The worker file handles /, /styles.css, /app.js, and /favicon.ico but has no case for /i18n.js (confirmed by searching the file). If the server runs in worker mode (via web-server-worker.ts), requests to /i18n.js will still return 404, leaving the web UI broken just as before this fix.
Summary
Follow-up fixes to PR #48. Resolves two issues discovered during runtime testing:
i18n.jscaused the entire web UI to fail to loadChanges
1. Fix: Use in-memory HNSW with auto-rebuild from SQLite (CRITICAL)
Problem: After PR #48 fixed the hnswlib-wasm API calls, runtime testing revealed that
hnswlib-wasmis compiled with-sENVIRONMENT=web(Emscripten), making it browser-only. In Node.js/Bun:"not compiled for this environment"FSmodule is not exported, so real filesystem to virtual filesystem bridging is impossiblewriteIndex()/readIndex()cannot persist to diskFix (
src/services/sqlite/hnsw-index.ts):globalThis.window = globalThispolyfill before loading hnswlib-wasm to bypass environment check.hnswbinary data.metafiles (id mapping) on real filesystem as beforeautoSaveFilenameconstructor arg to avoid unnecessary IDBFS sync noiseaddPoint(vector, label, false),searchKnn(vector, k, null)isPopulated()method to check if index has dataMath.min(k, count)guard to prevent searching empty indexesFix (
src/services/sqlite/vector-search.ts):searchInShard()detects an empty HNSW index, it automatically rebuilds from SQLite vectors viarebuildFromShard()before searchingArchitecture: All vectors are already stored in SQLite. HNSW indexes are now ephemeral acceleration structures rebuilt on-demand from the authoritative SQLite store.
2. Fix: Reduce IDBFS stderr noise
loadHnswlib()always attempts to mount IDBFS (browser IndexedDB), which fails in Node.js/Bun and prints warnings to stderr. Using a non-emptyautoSaveFilenamecauses additional IDBFS sync attempts on everyaddPoint()/markDelete(). Fix: use empty string asautoSaveFilename.3. Fix: Add i18n.js static file route to web server
Problem: PR #48 added Chinese localization via
src/web/i18n.js, butweb-server.tshad no route to serve this file. Browser requests to/i18n.jsreturned 404, causingt()function to be undefined, which crashedapp.jsinitialization. The web UI showed "Initializing..." with 0 memories despite the API working correctly.Fix (
src/services/web-server.ts): Add static file route for/i18n.jsalongside existing routes forapp.js,styles.css, etc.Verification
bun run typecheck— zero errorsbun test— 50 pass, 0 failmemory addsuccess,memory searchreturns results with correct similarity scoresFiles Changed
src/services/sqlite/hnsw-index.tssrc/services/sqlite/vector-search.tssrc/services/web-server.ts/i18n.jsstatic file route