Skip to content

Commit 2bcad93

Browse files
committed
feat: replace hnswlib-node with hnswlib-wasm for cross-platform compatibility
Remove native C++ dependency (hnswlib-node) that requires Visual Studio Build Tools on Windows. Replace with hnswlib-wasm which runs everywhere via WebAssembly. Based on the author's own wasm implementation from commit d5581bb, adapted for the current v2.11.3 dual-index architecture. Changes: - Replace hnswlib-node ^3.0.0 with hnswlib-wasm ^0.8.2 - Use lazy async import for hnswlib-wasm module - Adapt to wasm API: 3-arg constructor, sync read/writeIndex, Float32Array params - Remove vectorToArray() helper (wasm accepts Float32Array directly)
1 parent 5489dab commit 2bcad93

2 files changed

Lines changed: 22 additions & 17 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"@opencode-ai/plugin": "^1.0.162",
3737
"@xenova/transformers": "^2.17.2",
3838
"franc-min": "^6.2.0",
39-
"hnswlib-node": "^3.0.0",
39+
"hnswlib-wasm": "^0.8.2",
4040
"iso-639-3": "^3.0.1"
4141
},
4242
"devDependencies": {

src/services/sqlite/hnsw-index.ts

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,24 @@ import {
99
import { join, dirname, basename } from "node:path";
1010
import { log } from "../logger.js";
1111
import { CONFIG } from "../../config.js";
12-
import { HierarchicalNSW } from "hnswlib-node";
12+
13+
let HNSWLib: any = null;
14+
15+
async function loadHNSWLib(): Promise<any> {
16+
if (!HNSWLib) {
17+
const module = await import("hnswlib-wasm");
18+
HNSWLib = module;
19+
}
20+
return HNSWLib;
21+
}
1322

1423
export interface HNSWIndexData {
1524
id: string;
1625
vector: Float32Array;
1726
}
1827

1928
export class HNSWIndex {
20-
private index: HierarchicalNSW | null = null;
29+
private index: any = null;
2130
private idMap: Map<number, string> = new Map();
2231
private reverseMap: Map<string, number> = new Map();
2332
private nextId: number = 0;
@@ -34,15 +43,17 @@ export class HNSWIndex {
3443
private async ensureInitialized(): Promise<void> {
3544
if (this.initialized) return;
3645

46+
const hnsw = await loadHNSWLib();
47+
3748
const dir = dirname(this.indexPath);
3849
if (!existsSync(dir)) {
3950
mkdirSync(dir, { recursive: true });
4051
}
4152

4253
if (existsSync(this.indexPath)) {
4354
try {
44-
this.index = new HierarchicalNSW("cosine", this.dimensions);
45-
await this.index.readIndex(this.indexPath);
55+
this.index = new hnsw.HierarchicalNSW("cosine", this.dimensions);
56+
this.index.readIndex(this.indexPath);
4657

4758
const metaPath = this.indexPath + ".meta";
4859
if (existsSync(metaPath)) {
@@ -60,22 +71,16 @@ export class HNSWIndex {
6071
path: this.indexPath,
6172
error: String(error),
6273
});
63-
this.index = new HierarchicalNSW("cosine", this.dimensions);
64-
await this.index.initIndex(this.maxElements);
74+
this.index = new hnsw.HierarchicalNSW("cosine", this.dimensions, this.maxElements);
6575
}
6676
} else {
67-
this.index = new HierarchicalNSW("cosine", this.dimensions);
68-
await this.index.initIndex(this.maxElements);
77+
this.index = new hnsw.HierarchicalNSW("cosine", this.dimensions, this.maxElements);
6978
log("HNSW index created", { path: this.indexPath, dimensions: this.dimensions });
7079
}
7180

7281
this.initialized = true;
7382
}
7483

75-
private vectorToArray(vector: Float32Array): number[] {
76-
return Array.from(vector);
77-
}
78-
7984
async insert(id: string, vector: Float32Array): Promise<void> {
8085
await this.ensureInitialized();
8186

@@ -85,7 +90,7 @@ export class HNSWIndex {
8590
}
8691

8792
const internalId = this.nextId++;
88-
await this.index!.addPoint(this.vectorToArray(vector), internalId);
93+
this.index!.addPoint(vector, internalId);
8994
this.idMap.set(internalId, id);
9095
this.reverseMap.set(id, internalId);
9196

@@ -102,7 +107,7 @@ export class HNSWIndex {
102107
}
103108

104109
const internalId = this.nextId++;
105-
await this.index!.addPoint(this.vectorToArray(item.vector), internalId);
110+
this.index!.addPoint(item.vector, internalId);
106111
this.idMap.set(internalId, item.id);
107112
this.reverseMap.set(item.id, internalId);
108113
}
@@ -114,7 +119,7 @@ export class HNSWIndex {
114119
await this.ensureInitialized();
115120

116121
try {
117-
const results = await this.index!.searchKnn(this.vectorToArray(queryVector), k);
122+
const results = this.index!.searchKnn(queryVector, k);
118123

119124
return results.neighbors
120125
.map((internalId: number, idx: number) => ({
@@ -148,7 +153,7 @@ export class HNSWIndex {
148153
mkdirSync(dir, { recursive: true });
149154
}
150155

151-
await this.index.writeIndex(this.indexPath);
156+
this.index.writeIndex(this.indexPath);
152157

153158
const metaPath = this.indexPath + ".meta";
154159
const meta = {

0 commit comments

Comments
 (0)