@brainwires/idbvec 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -10
- package/package.json +1 -1
- package/pkg/bundler/LICENSE-APACHE +189 -0
- package/pkg/bundler/LICENSE-MIT +21 -0
- package/pkg/bundler/README.md +208 -0
- package/pkg/bundler/idbvec.d.ts +63 -0
- package/pkg/bundler/idbvec.js +9 -0
- package/pkg/bundler/idbvec_bg.js +687 -0
- package/pkg/bundler/idbvec_bg.wasm +0 -0
- package/pkg/bundler/idbvec_bg.wasm.d.ts +26 -0
- package/pkg/bundler/package.json +35 -0
- package/wrapper.d.ts +50 -1
- package/wrapper.js +115 -16
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "idbvec",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"collaborators": [
|
|
5
|
+
"nightness"
|
|
6
|
+
],
|
|
7
|
+
"description": "Client-side vector database built on IndexedDB with WASM",
|
|
8
|
+
"version": "0.2.0",
|
|
9
|
+
"license": "MIT OR Apache-2.0",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/Brainwires/idbvec"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"idbvec_bg.wasm",
|
|
16
|
+
"idbvec.js",
|
|
17
|
+
"idbvec_bg.js",
|
|
18
|
+
"idbvec.d.ts",
|
|
19
|
+
"LICENSE-APACHE",
|
|
20
|
+
"LICENSE-MIT"
|
|
21
|
+
],
|
|
22
|
+
"main": "idbvec.js",
|
|
23
|
+
"types": "idbvec.d.ts",
|
|
24
|
+
"sideEffects": [
|
|
25
|
+
"./idbvec.js",
|
|
26
|
+
"./snippets/*"
|
|
27
|
+
],
|
|
28
|
+
"keywords": [
|
|
29
|
+
"vector",
|
|
30
|
+
"database",
|
|
31
|
+
"wasm",
|
|
32
|
+
"indexeddb",
|
|
33
|
+
"hnsw"
|
|
34
|
+
]
|
|
35
|
+
}
|
package/wrapper.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export interface SearchResult {
|
|
10
10
|
id: string;
|
|
11
|
-
|
|
11
|
+
distance: number;
|
|
12
12
|
metadata?: Record<string, string>;
|
|
13
13
|
}
|
|
14
14
|
export interface VectorRecord {
|
|
@@ -16,15 +16,22 @@ export interface VectorRecord {
|
|
|
16
16
|
vector: Float32Array;
|
|
17
17
|
metadata?: Record<string, string>;
|
|
18
18
|
}
|
|
19
|
+
export interface GetResult {
|
|
20
|
+
id: string;
|
|
21
|
+
vector: Float32Array;
|
|
22
|
+
metadata?: Record<string, string>;
|
|
23
|
+
}
|
|
19
24
|
export interface SearchOptions {
|
|
20
25
|
k?: number;
|
|
21
26
|
ef?: number;
|
|
22
27
|
}
|
|
28
|
+
export type DistanceMetric = 'euclidean' | 'cosine' | 'dotproduct';
|
|
23
29
|
export interface VectorDBConfig {
|
|
24
30
|
name: string;
|
|
25
31
|
dimensions: number;
|
|
26
32
|
m?: number;
|
|
27
33
|
efConstruction?: number;
|
|
34
|
+
metric?: DistanceMetric;
|
|
28
35
|
}
|
|
29
36
|
/**
|
|
30
37
|
* Vector Database with IndexedDB persistence
|
|
@@ -33,6 +40,8 @@ export declare class VectorDatabase {
|
|
|
33
40
|
private wasmDB;
|
|
34
41
|
private idb;
|
|
35
42
|
private config;
|
|
43
|
+
private saveTimer;
|
|
44
|
+
private saveDebounceMs;
|
|
36
45
|
constructor(config: VectorDBConfig);
|
|
37
46
|
/**
|
|
38
47
|
* Initialize the database (load WASM + IndexedDB)
|
|
@@ -50,10 +59,26 @@ export declare class VectorDatabase {
|
|
|
50
59
|
* Search for nearest neighbors
|
|
51
60
|
*/
|
|
52
61
|
search(query: Float32Array, options?: SearchOptions): Promise<SearchResult[]>;
|
|
62
|
+
/**
|
|
63
|
+
* Get a vector and its metadata by ID
|
|
64
|
+
*/
|
|
65
|
+
get(id: string): Promise<GetResult | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Check if a vector exists by ID
|
|
68
|
+
*/
|
|
69
|
+
has(id: string): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* List all vector IDs
|
|
72
|
+
*/
|
|
73
|
+
listIds(): string[];
|
|
53
74
|
/**
|
|
54
75
|
* Delete a vector by ID
|
|
55
76
|
*/
|
|
56
77
|
delete(id: string): Promise<boolean>;
|
|
78
|
+
/**
|
|
79
|
+
* Delete multiple vectors by ID
|
|
80
|
+
*/
|
|
81
|
+
deleteBatch(ids: string[]): Promise<number>;
|
|
57
82
|
/**
|
|
58
83
|
* Get total number of vectors
|
|
59
84
|
*/
|
|
@@ -62,10 +87,30 @@ export declare class VectorDatabase {
|
|
|
62
87
|
* Clear all data
|
|
63
88
|
*/
|
|
64
89
|
clear(): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Export the database state as a JSON string
|
|
92
|
+
*/
|
|
93
|
+
exportData(): string;
|
|
94
|
+
/**
|
|
95
|
+
* Import database state from a JSON string
|
|
96
|
+
*/
|
|
97
|
+
importData(json: string): Promise<void>;
|
|
98
|
+
/**
|
|
99
|
+
* Flush any pending saves to IndexedDB immediately
|
|
100
|
+
*/
|
|
101
|
+
flush(): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Destroy the database completely (deletes IndexedDB)
|
|
104
|
+
*/
|
|
105
|
+
destroy(): Promise<void>;
|
|
65
106
|
/**
|
|
66
107
|
* Open IndexedDB connection
|
|
67
108
|
*/
|
|
68
109
|
private openIndexedDB;
|
|
110
|
+
/**
|
|
111
|
+
* Debounced save to IndexedDB
|
|
112
|
+
*/
|
|
113
|
+
private debounceSave;
|
|
69
114
|
/**
|
|
70
115
|
* Save WASM state to IndexedDB
|
|
71
116
|
*/
|
|
@@ -78,6 +123,10 @@ export declare class VectorDatabase {
|
|
|
78
123
|
* Close the database
|
|
79
124
|
*/
|
|
80
125
|
close(): void;
|
|
126
|
+
/**
|
|
127
|
+
* Support for explicit resource management (using syntax)
|
|
128
|
+
*/
|
|
129
|
+
[Symbol.dispose](): void;
|
|
81
130
|
}
|
|
82
131
|
/**
|
|
83
132
|
* Standalone distance functions
|
package/wrapper.js
CHANGED
|
@@ -13,12 +13,15 @@ export class VectorDatabase {
|
|
|
13
13
|
wasmDB = null;
|
|
14
14
|
idb = null;
|
|
15
15
|
config;
|
|
16
|
+
saveTimer = null;
|
|
17
|
+
saveDebounceMs = 1000;
|
|
16
18
|
constructor(config) {
|
|
17
19
|
this.config = {
|
|
18
20
|
name: config.name,
|
|
19
21
|
dimensions: config.dimensions,
|
|
20
22
|
m: config.m ?? 16,
|
|
21
23
|
efConstruction: config.efConstruction ?? 200,
|
|
24
|
+
metric: config.metric ?? 'euclidean',
|
|
22
25
|
};
|
|
23
26
|
}
|
|
24
27
|
/**
|
|
@@ -33,11 +36,9 @@ export class VectorDatabase {
|
|
|
33
36
|
const saved = await this.loadFromIndexedDB();
|
|
34
37
|
if (saved) {
|
|
35
38
|
this.wasmDB = wasmModule.VectorDB.deserialize(saved);
|
|
36
|
-
// console.log('Restored VectorDB from IndexedDB')
|
|
37
39
|
}
|
|
38
40
|
else {
|
|
39
|
-
this.wasmDB = new wasmModule.VectorDB(this.config.dimensions, this.config.m, this.config.efConstruction);
|
|
40
|
-
// console.log('Created new VectorDB')
|
|
41
|
+
this.wasmDB = new wasmModule.VectorDB(this.config.dimensions, this.config.m, this.config.efConstruction, this.config.metric);
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
/**
|
|
@@ -47,8 +48,8 @@ export class VectorDatabase {
|
|
|
47
48
|
if (!this.wasmDB)
|
|
48
49
|
throw new Error('Database not initialized');
|
|
49
50
|
this.wasmDB.insert(id, vector, metadata ?? null);
|
|
50
|
-
//
|
|
51
|
-
|
|
51
|
+
// Debounced persistence
|
|
52
|
+
this.debounceSave();
|
|
52
53
|
}
|
|
53
54
|
/**
|
|
54
55
|
* Batch insert multiple vectors
|
|
@@ -57,13 +58,6 @@ export class VectorDatabase {
|
|
|
57
58
|
if (!this.wasmDB)
|
|
58
59
|
throw new Error('Database not initialized');
|
|
59
60
|
for (const record of records) {
|
|
60
|
-
// console.log('🔍 Inserting to WASM:', {
|
|
61
|
-
// id: record.id,
|
|
62
|
-
// vectorLength: record.vector.length,
|
|
63
|
-
// metadata: record.metadata,
|
|
64
|
-
// metadataType: typeof record.metadata,
|
|
65
|
-
// metadataKeys: record.metadata ? Object.keys(record.metadata) : null
|
|
66
|
-
// })
|
|
67
61
|
this.wasmDB.insert(record.id, record.vector, record.metadata ?? null);
|
|
68
62
|
}
|
|
69
63
|
await this.saveToIndexedDB();
|
|
@@ -79,6 +73,33 @@ export class VectorDatabase {
|
|
|
79
73
|
const results = this.wasmDB.search(query, k, ef);
|
|
80
74
|
return results;
|
|
81
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Get a vector and its metadata by ID
|
|
78
|
+
*/
|
|
79
|
+
async get(id) {
|
|
80
|
+
if (!this.wasmDB)
|
|
81
|
+
throw new Error('Database not initialized');
|
|
82
|
+
const result = this.wasmDB.get(id);
|
|
83
|
+
if (result === null || result === undefined)
|
|
84
|
+
return null;
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Check if a vector exists by ID
|
|
89
|
+
*/
|
|
90
|
+
has(id) {
|
|
91
|
+
if (!this.wasmDB)
|
|
92
|
+
throw new Error('Database not initialized');
|
|
93
|
+
return this.wasmDB.has(id);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* List all vector IDs
|
|
97
|
+
*/
|
|
98
|
+
listIds() {
|
|
99
|
+
if (!this.wasmDB)
|
|
100
|
+
throw new Error('Database not initialized');
|
|
101
|
+
return this.wasmDB.list_ids();
|
|
102
|
+
}
|
|
82
103
|
/**
|
|
83
104
|
* Delete a vector by ID
|
|
84
105
|
*/
|
|
@@ -87,10 +108,22 @@ export class VectorDatabase {
|
|
|
87
108
|
throw new Error('Database not initialized');
|
|
88
109
|
const deleted = this.wasmDB.delete(id);
|
|
89
110
|
if (deleted) {
|
|
90
|
-
|
|
111
|
+
this.debounceSave();
|
|
91
112
|
}
|
|
92
113
|
return deleted;
|
|
93
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Delete multiple vectors by ID
|
|
117
|
+
*/
|
|
118
|
+
async deleteBatch(ids) {
|
|
119
|
+
if (!this.wasmDB)
|
|
120
|
+
throw new Error('Database not initialized');
|
|
121
|
+
const count = this.wasmDB.delete_batch(ids);
|
|
122
|
+
if (count > 0) {
|
|
123
|
+
await this.saveToIndexedDB();
|
|
124
|
+
}
|
|
125
|
+
return count;
|
|
126
|
+
}
|
|
94
127
|
/**
|
|
95
128
|
* Get total number of vectors
|
|
96
129
|
*/
|
|
@@ -106,9 +139,50 @@ export class VectorDatabase {
|
|
|
106
139
|
if (!this.wasmDB)
|
|
107
140
|
throw new Error('Database not initialized');
|
|
108
141
|
const wasmModule = await import('./pkg/bundler/idbvec');
|
|
109
|
-
this.wasmDB
|
|
142
|
+
this.wasmDB.free();
|
|
143
|
+
this.wasmDB = new wasmModule.VectorDB(this.config.dimensions, this.config.m, this.config.efConstruction, this.config.metric);
|
|
144
|
+
await this.saveToIndexedDB();
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Export the database state as a JSON string
|
|
148
|
+
*/
|
|
149
|
+
exportData() {
|
|
150
|
+
if (!this.wasmDB)
|
|
151
|
+
throw new Error('Database not initialized');
|
|
152
|
+
return this.wasmDB.serialize();
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Import database state from a JSON string
|
|
156
|
+
*/
|
|
157
|
+
async importData(json) {
|
|
158
|
+
if (!this.wasmDB)
|
|
159
|
+
throw new Error('Database not initialized');
|
|
160
|
+
const wasmModule = await import('./pkg/bundler/idbvec');
|
|
161
|
+
this.wasmDB.free();
|
|
162
|
+
this.wasmDB = wasmModule.VectorDB.deserialize(json);
|
|
110
163
|
await this.saveToIndexedDB();
|
|
111
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Flush any pending saves to IndexedDB immediately
|
|
167
|
+
*/
|
|
168
|
+
async flush() {
|
|
169
|
+
if (this.saveTimer) {
|
|
170
|
+
clearTimeout(this.saveTimer);
|
|
171
|
+
this.saveTimer = null;
|
|
172
|
+
}
|
|
173
|
+
await this.saveToIndexedDB();
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Destroy the database completely (deletes IndexedDB)
|
|
177
|
+
*/
|
|
178
|
+
async destroy() {
|
|
179
|
+
this.close();
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
const request = indexedDB.deleteDatabase(this.config.name);
|
|
182
|
+
request.onerror = () => reject(request.error);
|
|
183
|
+
request.onsuccess = () => resolve();
|
|
184
|
+
});
|
|
185
|
+
}
|
|
112
186
|
/**
|
|
113
187
|
* Open IndexedDB connection
|
|
114
188
|
*/
|
|
@@ -121,7 +195,6 @@ export class VectorDatabase {
|
|
|
121
195
|
// CRITICAL: Handle versionchange event to auto-close when database is being deleted
|
|
122
196
|
// This prevents "blocked" errors when trying to delete the database
|
|
123
197
|
db.onversionchange = () => {
|
|
124
|
-
// console.log(`🔒 VectorDB "${this.config.name}" received versionchange event - closing connection`)
|
|
125
198
|
db.close();
|
|
126
199
|
this.idb = null;
|
|
127
200
|
};
|
|
@@ -135,6 +208,18 @@ export class VectorDatabase {
|
|
|
135
208
|
};
|
|
136
209
|
});
|
|
137
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Debounced save to IndexedDB
|
|
213
|
+
*/
|
|
214
|
+
debounceSave() {
|
|
215
|
+
if (this.saveTimer) {
|
|
216
|
+
clearTimeout(this.saveTimer);
|
|
217
|
+
}
|
|
218
|
+
this.saveTimer = setTimeout(() => {
|
|
219
|
+
this.saveTimer = null;
|
|
220
|
+
this.saveToIndexedDB();
|
|
221
|
+
}, this.saveDebounceMs);
|
|
222
|
+
}
|
|
138
223
|
/**
|
|
139
224
|
* Save WASM state to IndexedDB
|
|
140
225
|
*/
|
|
@@ -171,11 +256,25 @@ export class VectorDatabase {
|
|
|
171
256
|
* Close the database
|
|
172
257
|
*/
|
|
173
258
|
close() {
|
|
259
|
+
// Flush pending saves
|
|
260
|
+
if (this.saveTimer) {
|
|
261
|
+
clearTimeout(this.saveTimer);
|
|
262
|
+
this.saveTimer = null;
|
|
263
|
+
}
|
|
174
264
|
if (this.idb) {
|
|
175
265
|
this.idb.close();
|
|
176
266
|
this.idb = null;
|
|
177
267
|
}
|
|
178
|
-
this.wasmDB
|
|
268
|
+
if (this.wasmDB) {
|
|
269
|
+
this.wasmDB.free();
|
|
270
|
+
this.wasmDB = null;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Support for explicit resource management (using syntax)
|
|
275
|
+
*/
|
|
276
|
+
[Symbol.dispose]() {
|
|
277
|
+
this.close();
|
|
179
278
|
}
|
|
180
279
|
}
|
|
181
280
|
/**
|