eigen-db 4.2.0 → 4.4.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/src/lib/simd.wat CHANGED
@@ -4,70 +4,38 @@
4
4
 
5
5
  ;; normalize(ptr: i32, dimensions: i32)
6
6
  ;; Normalizes a vector in-place to unit length using SIMD.
7
- ;; Optimized with 4x loop unrolling (16 floats/iteration) and multiple accumulators.
8
7
  (func (export "normalize") (param $ptr i32) (param $dim i32)
9
8
  (local $i i32)
10
- (local $acc0 v128)
11
- (local $acc1 v128)
12
- (local $acc2 v128)
13
- (local $acc3 v128)
9
+ (local $acc v128)
14
10
  (local $sum f32)
15
11
  (local $mag f32)
16
12
  (local $inv_mag f32)
17
13
  (local $inv_vec v128)
18
- (local $unroll_end i32)
19
14
  (local $simd_end i32)
20
15
  (local $offset i32)
21
16
 
22
- ;; Phase 1: Sum of squares with 4x unroll and 4 independent accumulators
23
- (local.set $acc0 (v128.const f32x4 0 0 0 0))
24
- (local.set $acc1 (v128.const f32x4 0 0 0 0))
25
- (local.set $acc2 (v128.const f32x4 0 0 0 0))
26
- (local.set $acc3 (v128.const f32x4 0 0 0 0))
27
- (local.set $unroll_end (i32.and (local.get $dim) (i32.const -16)))
17
+ ;; Phase 1: Sum of squares
18
+ (local.set $acc (v128.const f32x4 0 0 0 0))
28
19
  (local.set $simd_end (i32.and (local.get $dim) (i32.const -4)))
29
20
  (local.set $i (i32.const 0))
30
21
 
31
- (block $break_sum_u
32
- (loop $loop_sum_u
33
- (br_if $break_sum_u (i32.ge_u (local.get $i) (local.get $unroll_end)))
22
+ ;; SIMD loop: 4 floats per iteration
23
+ (block $break_sum
24
+ (loop $loop_sum
25
+ (br_if $break_sum (i32.ge_u (local.get $i) (local.get $simd_end)))
34
26
  (local.set $offset (i32.add (local.get $ptr) (i32.shl (local.get $i) (i32.const 2))))
35
-
36
- (local.set $acc0 (f32x4.add (local.get $acc0)
37
- (f32x4.mul (v128.load (local.get $offset)) (v128.load (local.get $offset)))))
38
- (local.set $acc1 (f32x4.add (local.get $acc1)
39
- (f32x4.mul (v128.load offset=16 (local.get $offset)) (v128.load offset=16 (local.get $offset)))))
40
- (local.set $acc2 (f32x4.add (local.get $acc2)
41
- (f32x4.mul (v128.load offset=32 (local.get $offset)) (v128.load offset=32 (local.get $offset)))))
42
- (local.set $acc3 (f32x4.add (local.get $acc3)
43
- (f32x4.mul (v128.load offset=48 (local.get $offset)) (v128.load offset=48 (local.get $offset)))))
44
-
45
- (local.set $i (i32.add (local.get $i) (i32.const 16)))
46
- (br $loop_sum_u)
47
- )
48
- )
49
-
50
- ;; Merge 4 accumulators
51
- (local.set $acc0 (f32x4.add (f32x4.add (local.get $acc0) (local.get $acc1))
52
- (f32x4.add (local.get $acc2) (local.get $acc3))))
53
-
54
- ;; Remaining 4-wide chunks
55
- (block $break_sum4
56
- (loop $loop_sum4
57
- (br_if $break_sum4 (i32.ge_u (local.get $i) (local.get $simd_end)))
58
- (local.set $offset (i32.add (local.get $ptr) (i32.shl (local.get $i) (i32.const 2))))
59
- (local.set $acc0 (f32x4.add (local.get $acc0)
27
+ (local.set $acc (f32x4.add (local.get $acc)
60
28
  (f32x4.mul (v128.load (local.get $offset)) (v128.load (local.get $offset)))))
61
29
  (local.set $i (i32.add (local.get $i) (i32.const 4)))
62
- (br $loop_sum4)
30
+ (br $loop_sum)
63
31
  )
64
32
  )
65
33
 
66
34
  ;; Horizontal sum
67
35
  (local.set $sum
68
36
  (f32.add
69
- (f32.add (f32x4.extract_lane 0 (local.get $acc0)) (f32x4.extract_lane 1 (local.get $acc0)))
70
- (f32.add (f32x4.extract_lane 2 (local.get $acc0)) (f32x4.extract_lane 3 (local.get $acc0)))))
37
+ (f32.add (f32x4.extract_lane 0 (local.get $acc)) (f32x4.extract_lane 1 (local.get $acc)))
38
+ (f32.add (f32x4.extract_lane 2 (local.get $acc)) (f32x4.extract_lane 3 (local.get $acc)))))
71
39
 
72
40
  ;; Scalar remainder
73
41
  (block $break_rem_sum
@@ -86,39 +54,20 @@
86
54
  (if (f32.eq (local.get $mag) (f32.const 0))
87
55
  (then (return)))
88
56
 
89
- ;; Phase 2: Scale by inverse magnitude (4x unrolled)
57
+ ;; Phase 2: Scale by inverse magnitude
90
58
  (local.set $inv_mag (f32.div (f32.const 1) (local.get $mag)))
91
59
  (local.set $inv_vec (f32x4.splat (local.get $inv_mag)))
92
60
  (local.set $i (i32.const 0))
93
61
 
94
- (block $break_norm_u
95
- (loop $loop_norm_u
96
- (br_if $break_norm_u (i32.ge_u (local.get $i) (local.get $unroll_end)))
97
- (local.set $offset (i32.add (local.get $ptr) (i32.shl (local.get $i) (i32.const 2))))
98
-
99
- (v128.store (local.get $offset)
100
- (f32x4.mul (v128.load (local.get $offset)) (local.get $inv_vec)))
101
- (v128.store offset=16 (local.get $offset)
102
- (f32x4.mul (v128.load offset=16 (local.get $offset)) (local.get $inv_vec)))
103
- (v128.store offset=32 (local.get $offset)
104
- (f32x4.mul (v128.load offset=32 (local.get $offset)) (local.get $inv_vec)))
105
- (v128.store offset=48 (local.get $offset)
106
- (f32x4.mul (v128.load offset=48 (local.get $offset)) (local.get $inv_vec)))
107
-
108
- (local.set $i (i32.add (local.get $i) (i32.const 16)))
109
- (br $loop_norm_u)
110
- )
111
- )
112
-
113
- ;; Remaining 4-wide chunks
114
- (block $break_norm4
115
- (loop $loop_norm4
116
- (br_if $break_norm4 (i32.ge_u (local.get $i) (local.get $simd_end)))
62
+ ;; SIMD loop
63
+ (block $break_norm
64
+ (loop $loop_norm
65
+ (br_if $break_norm (i32.ge_u (local.get $i) (local.get $simd_end)))
117
66
  (local.set $offset (i32.add (local.get $ptr) (i32.shl (local.get $i) (i32.const 2))))
118
67
  (v128.store (local.get $offset)
119
68
  (f32x4.mul (v128.load (local.get $offset)) (local.get $inv_vec)))
120
69
  (local.set $i (i32.add (local.get $i) (i32.const 4)))
121
- (br $loop_norm4)
70
+ (br $loop_norm)
122
71
  )
123
72
  )
124
73
 
@@ -137,226 +86,71 @@
137
86
 
138
87
  ;; search_all(query_ptr, db_ptr, scores_ptr, db_size, dimensions)
139
88
  ;; Computes dot products of query against every vector in the database.
140
- ;; Optimized with:
141
- ;; - 2-vector outer loop unrolling (halves query memory reads)
142
- ;; - 4x inner loop unrolling (16 floats/iteration, 4 accumulators per vector)
143
89
  (func (export "search_all") (param $query_ptr i32) (param $db_ptr i32) (param $scores_ptr i32) (param $db_size i32) (param $dim i32)
144
90
  (local $i i32)
145
91
  (local $j i32)
146
- (local $accA0 v128)
147
- (local $accA1 v128)
148
- (local $accA2 v128)
149
- (local $accA3 v128)
150
- (local $accB0 v128)
151
- (local $accB1 v128)
152
- (local $accB2 v128)
153
- (local $accB3 v128)
154
- (local $q0 v128)
155
- (local $q1 v128)
156
- (local $q2 v128)
157
- (local $q3 v128)
158
- (local $dotA f32)
159
- (local $dotB f32)
160
- (local $vec_ptrA i32)
161
- (local $vec_ptrB i32)
162
- (local $unroll_end i32)
92
+ (local $acc v128)
93
+ (local $dot f32)
94
+ (local $vec_ptr i32)
163
95
  (local $simd_end i32)
164
96
  (local $q_offset i32)
165
- (local $vA_offset i32)
166
- (local $vB_offset i32)
97
+ (local $v_offset i32)
167
98
  (local $bytes_per_vec i32)
168
- (local $pair_end i32)
169
99
 
170
- (local.set $unroll_end (i32.and (local.get $dim) (i32.const -16)))
171
100
  (local.set $simd_end (i32.and (local.get $dim) (i32.const -4)))
172
101
  (local.set $bytes_per_vec (i32.shl (local.get $dim) (i32.const 2)))
173
- (local.set $pair_end (i32.and (local.get $db_size) (i32.const -2)))
174
102
  (local.set $i (i32.const 0))
175
103
 
176
- ;; Main loop: process 2 database vectors per iteration
104
+ ;; Outer loop: one database vector per iteration
177
105
  (block $break_outer
178
106
  (loop $loop_outer
179
- (br_if $break_outer (i32.ge_u (local.get $i) (local.get $pair_end)))
107
+ (br_if $break_outer (i32.ge_u (local.get $i) (local.get $db_size)))
180
108
 
181
- (local.set $vec_ptrA
109
+ (local.set $vec_ptr
182
110
  (i32.add (local.get $db_ptr) (i32.mul (local.get $i) (local.get $bytes_per_vec))))
183
- (local.set $vec_ptrB
184
- (i32.add (local.get $vec_ptrA) (local.get $bytes_per_vec)))
185
-
186
- (local.set $accA0 (v128.const f32x4 0 0 0 0))
187
- (local.set $accA1 (v128.const f32x4 0 0 0 0))
188
- (local.set $accA2 (v128.const f32x4 0 0 0 0))
189
- (local.set $accA3 (v128.const f32x4 0 0 0 0))
190
- (local.set $accB0 (v128.const f32x4 0 0 0 0))
191
- (local.set $accB1 (v128.const f32x4 0 0 0 0))
192
- (local.set $accB2 (v128.const f32x4 0 0 0 0))
193
- (local.set $accB3 (v128.const f32x4 0 0 0 0))
111
+ (local.set $acc (v128.const f32x4 0 0 0 0))
194
112
  (local.set $j (i32.const 0))
195
113
 
196
- ;; Inner loop: load query once, dot product against both vectors
114
+ ;; SIMD inner loop: dot product, 4 floats per iteration
197
115
  (block $break_inner
198
116
  (loop $loop_inner
199
- (br_if $break_inner (i32.ge_u (local.get $j) (local.get $unroll_end)))
200
- (local.set $q_offset (i32.add (local.get $query_ptr) (i32.shl (local.get $j) (i32.const 2))))
201
- (local.set $vA_offset (i32.add (local.get $vec_ptrA) (i32.shl (local.get $j) (i32.const 2))))
202
- (local.set $vB_offset (i32.add (local.get $vec_ptrB) (i32.shl (local.get $j) (i32.const 2))))
203
-
204
- ;; Load query chunk (shared between both vectors)
205
- (local.set $q0 (v128.load (local.get $q_offset)))
206
- (local.set $q1 (v128.load offset=16 (local.get $q_offset)))
207
- (local.set $q2 (v128.load offset=32 (local.get $q_offset)))
208
- (local.set $q3 (v128.load offset=48 (local.get $q_offset)))
209
-
210
- ;; Vector A
211
- (local.set $accA0 (f32x4.add (local.get $accA0)
212
- (f32x4.mul (local.get $q0) (v128.load (local.get $vA_offset)))))
213
- (local.set $accA1 (f32x4.add (local.get $accA1)
214
- (f32x4.mul (local.get $q1) (v128.load offset=16 (local.get $vA_offset)))))
215
- (local.set $accA2 (f32x4.add (local.get $accA2)
216
- (f32x4.mul (local.get $q2) (v128.load offset=32 (local.get $vA_offset)))))
217
- (local.set $accA3 (f32x4.add (local.get $accA3)
218
- (f32x4.mul (local.get $q3) (v128.load offset=48 (local.get $vA_offset)))))
219
-
220
- ;; Vector B (reuses query loads)
221
- (local.set $accB0 (f32x4.add (local.get $accB0)
222
- (f32x4.mul (local.get $q0) (v128.load (local.get $vB_offset)))))
223
- (local.set $accB1 (f32x4.add (local.get $accB1)
224
- (f32x4.mul (local.get $q1) (v128.load offset=16 (local.get $vB_offset)))))
225
- (local.set $accB2 (f32x4.add (local.get $accB2)
226
- (f32x4.mul (local.get $q2) (v128.load offset=32 (local.get $vB_offset)))))
227
- (local.set $accB3 (f32x4.add (local.get $accB3)
228
- (f32x4.mul (local.get $q3) (v128.load offset=48 (local.get $vB_offset)))))
229
-
230
- (local.set $j (i32.add (local.get $j) (i32.const 16)))
231
- (br $loop_inner)
232
- )
233
- )
234
-
235
- ;; Merge accumulators
236
- (local.set $accA0 (f32x4.add (f32x4.add (local.get $accA0) (local.get $accA1))
237
- (f32x4.add (local.get $accA2) (local.get $accA3))))
238
- (local.set $accB0 (f32x4.add (f32x4.add (local.get $accB0) (local.get $accB1))
239
- (f32x4.add (local.get $accB2) (local.get $accB3))))
240
-
241
- ;; 4-wide cleanup (both vectors)
242
- (block $break_inner4
243
- (loop $loop_inner4
244
- (br_if $break_inner4 (i32.ge_u (local.get $j) (local.get $simd_end)))
117
+ (br_if $break_inner (i32.ge_u (local.get $j) (local.get $simd_end)))
245
118
  (local.set $q_offset (i32.add (local.get $query_ptr) (i32.shl (local.get $j) (i32.const 2))))
246
- (local.set $vA_offset (i32.add (local.get $vec_ptrA) (i32.shl (local.get $j) (i32.const 2))))
247
- (local.set $vB_offset (i32.add (local.get $vec_ptrB) (i32.shl (local.get $j) (i32.const 2))))
248
- (local.set $q0 (v128.load (local.get $q_offset)))
249
- (local.set $accA0 (f32x4.add (local.get $accA0) (f32x4.mul (local.get $q0) (v128.load (local.get $vA_offset)))))
250
- (local.set $accB0 (f32x4.add (local.get $accB0) (f32x4.mul (local.get $q0) (v128.load (local.get $vB_offset)))))
119
+ (local.set $v_offset (i32.add (local.get $vec_ptr) (i32.shl (local.get $j) (i32.const 2))))
120
+ (local.set $acc (f32x4.add (local.get $acc)
121
+ (f32x4.mul (v128.load (local.get $q_offset)) (v128.load (local.get $v_offset)))))
251
122
  (local.set $j (i32.add (local.get $j) (i32.const 4)))
252
- (br $loop_inner4)
123
+ (br $loop_inner)
253
124
  )
254
125
  )
255
126
 
256
- ;; Horizontal sums
257
- (local.set $dotA
127
+ ;; Horizontal sum
128
+ (local.set $dot
258
129
  (f32.add
259
- (f32.add (f32x4.extract_lane 0 (local.get $accA0)) (f32x4.extract_lane 1 (local.get $accA0)))
260
- (f32.add (f32x4.extract_lane 2 (local.get $accA0)) (f32x4.extract_lane 3 (local.get $accA0)))))
261
- (local.set $dotB
262
- (f32.add
263
- (f32.add (f32x4.extract_lane 0 (local.get $accB0)) (f32x4.extract_lane 1 (local.get $accB0)))
264
- (f32.add (f32x4.extract_lane 2 (local.get $accB0)) (f32x4.extract_lane 3 (local.get $accB0)))))
130
+ (f32.add (f32x4.extract_lane 0 (local.get $acc)) (f32x4.extract_lane 1 (local.get $acc)))
131
+ (f32.add (f32x4.extract_lane 2 (local.get $acc)) (f32x4.extract_lane 3 (local.get $acc)))))
265
132
 
266
- ;; Scalar remainder (both vectors)
133
+ ;; Scalar remainder
267
134
  (block $break_rem
268
135
  (loop $loop_rem
269
136
  (br_if $break_rem (i32.ge_u (local.get $j) (local.get $dim)))
270
137
  (local.set $q_offset (i32.add (local.get $query_ptr) (i32.shl (local.get $j) (i32.const 2))))
271
- (local.set $vA_offset (i32.add (local.get $vec_ptrA) (i32.shl (local.get $j) (i32.const 2))))
272
- (local.set $vB_offset (i32.add (local.get $vec_ptrB) (i32.shl (local.get $j) (i32.const 2))))
273
- (local.set $dotA (f32.add (local.get $dotA)
274
- (f32.mul (f32.load (local.get $q_offset)) (f32.load (local.get $vA_offset)))))
275
- (local.set $dotB (f32.add (local.get $dotB)
276
- (f32.mul (f32.load (local.get $q_offset)) (f32.load (local.get $vB_offset)))))
138
+ (local.set $v_offset (i32.add (local.get $vec_ptr) (i32.shl (local.get $j) (i32.const 2))))
139
+ (local.set $dot (f32.add (local.get $dot)
140
+ (f32.mul (f32.load (local.get $q_offset)) (f32.load (local.get $v_offset)))))
277
141
  (local.set $j (i32.add (local.get $j) (i32.const 1)))
278
142
  (br $loop_rem)
279
143
  )
280
144
  )
281
145
 
282
- ;; Store scores for vectors i and i+1
146
+ ;; Store score
283
147
  (f32.store
284
148
  (i32.add (local.get $scores_ptr) (i32.shl (local.get $i) (i32.const 2)))
285
- (local.get $dotA))
286
- (f32.store
287
- (i32.add (local.get $scores_ptr) (i32.shl (i32.add (local.get $i) (i32.const 1)) (i32.const 2)))
288
- (local.get $dotB))
149
+ (local.get $dot))
289
150
 
290
- (local.set $i (i32.add (local.get $i) (i32.const 2)))
151
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
291
152
  (br $loop_outer)
292
153
  )
293
154
  )
294
-
295
- ;; Handle last vector if db_size is odd
296
- (if (i32.lt_u (local.get $i) (local.get $db_size))
297
- (then
298
- (local.set $vec_ptrA
299
- (i32.add (local.get $db_ptr) (i32.mul (local.get $i) (local.get $bytes_per_vec))))
300
- (local.set $accA0 (v128.const f32x4 0 0 0 0))
301
- (local.set $accA1 (v128.const f32x4 0 0 0 0))
302
- (local.set $accA2 (v128.const f32x4 0 0 0 0))
303
- (local.set $accA3 (v128.const f32x4 0 0 0 0))
304
- (local.set $j (i32.const 0))
305
-
306
- (block $break_last
307
- (loop $loop_last
308
- (br_if $break_last (i32.ge_u (local.get $j) (local.get $unroll_end)))
309
- (local.set $q_offset (i32.add (local.get $query_ptr) (i32.shl (local.get $j) (i32.const 2))))
310
- (local.set $vA_offset (i32.add (local.get $vec_ptrA) (i32.shl (local.get $j) (i32.const 2))))
311
- (local.set $accA0 (f32x4.add (local.get $accA0)
312
- (f32x4.mul (v128.load (local.get $q_offset)) (v128.load (local.get $vA_offset)))))
313
- (local.set $accA1 (f32x4.add (local.get $accA1)
314
- (f32x4.mul (v128.load offset=16 (local.get $q_offset)) (v128.load offset=16 (local.get $vA_offset)))))
315
- (local.set $accA2 (f32x4.add (local.get $accA2)
316
- (f32x4.mul (v128.load offset=32 (local.get $q_offset)) (v128.load offset=32 (local.get $vA_offset)))))
317
- (local.set $accA3 (f32x4.add (local.get $accA3)
318
- (f32x4.mul (v128.load offset=48 (local.get $q_offset)) (v128.load offset=48 (local.get $vA_offset)))))
319
- (local.set $j (i32.add (local.get $j) (i32.const 16)))
320
- (br $loop_last)
321
- )
322
- )
323
-
324
- (local.set $accA0 (f32x4.add (f32x4.add (local.get $accA0) (local.get $accA1))
325
- (f32x4.add (local.get $accA2) (local.get $accA3))))
326
-
327
- (block $break_last4
328
- (loop $loop_last4
329
- (br_if $break_last4 (i32.ge_u (local.get $j) (local.get $simd_end)))
330
- (local.set $q_offset (i32.add (local.get $query_ptr) (i32.shl (local.get $j) (i32.const 2))))
331
- (local.set $vA_offset (i32.add (local.get $vec_ptrA) (i32.shl (local.get $j) (i32.const 2))))
332
- (local.set $accA0 (f32x4.add (local.get $accA0)
333
- (f32x4.mul (v128.load (local.get $q_offset)) (v128.load (local.get $vA_offset)))))
334
- (local.set $j (i32.add (local.get $j) (i32.const 4)))
335
- (br $loop_last4)
336
- )
337
- )
338
-
339
- (local.set $dotA
340
- (f32.add
341
- (f32.add (f32x4.extract_lane 0 (local.get $accA0)) (f32x4.extract_lane 1 (local.get $accA0)))
342
- (f32.add (f32x4.extract_lane 2 (local.get $accA0)) (f32x4.extract_lane 3 (local.get $accA0)))))
343
-
344
- (block $break_last_rem
345
- (loop $loop_last_rem
346
- (br_if $break_last_rem (i32.ge_u (local.get $j) (local.get $dim)))
347
- (local.set $q_offset (i32.add (local.get $query_ptr) (i32.shl (local.get $j) (i32.const 2))))
348
- (local.set $vA_offset (i32.add (local.get $vec_ptrA) (i32.shl (local.get $j) (i32.const 2))))
349
- (local.set $dotA (f32.add (local.get $dotA)
350
- (f32.mul (f32.load (local.get $q_offset)) (f32.load (local.get $vA_offset)))))
351
- (local.set $j (i32.add (local.get $j) (i32.const 1)))
352
- (br $loop_last_rem)
353
- )
354
- )
355
-
356
- (f32.store
357
- (i32.add (local.get $scores_ptr) (i32.shl (local.get $i) (i32.const 2)))
358
- (local.get $dotA))
359
- )
360
- )
361
155
  )
362
156
  )
package/src/lib/types.ts CHANGED
@@ -2,20 +2,18 @@
2
2
  * Options for opening a VectorDB instance.
3
3
  */
4
4
  export interface OpenOptions {
5
- /** Name of the OPFS directory. Defaults to "default". */
6
- name?: string;
7
5
  /** Vector dimensions (e.g., 1536 for OpenAI text-embedding-3-small) */
8
6
  dimensions: number;
9
7
  /** Whether to normalize vectors on set/query (default: true). */
10
8
  normalize?: boolean;
9
+ /** Storage provider for persistence. Defaults to InMemoryStorageProvider. Use OPFSStorageProvider for browser persistence. */
10
+ storage?: import("./storage").StorageProvider;
11
11
  }
12
12
 
13
13
  /**
14
14
  * Extended options including internal overrides for testing/advanced use.
15
15
  */
16
16
  export interface OpenOptionsInternal extends OpenOptions {
17
- /** Override the storage provider (default: OPFS). Useful for testing. */
18
- storage?: import("./storage").StorageProvider;
19
17
  /** Pre-compiled WASM binary. If not provided, uses the embedded SIMD binary. Set to null to force JS-only mode. */
20
18
  wasmBinary?: Uint8Array | null;
21
19
  }
@@ -20,7 +20,7 @@ import type { ResultItem } from "./result-set";
20
20
  import { iterableResults, topKResults } from "./result-set";
21
21
  import { getSimdWasmBinary } from "./simd-binary";
22
22
  import type { StorageProvider } from "./storage";
23
- import { OPFSStorageProvider } from "./storage";
23
+ import { InMemoryStorageProvider } from "./storage";
24
24
  import type { OpenOptions, OpenOptionsInternal, QueryOptions, SetOptions, VectorInput } from "./types";
25
25
  import { instantiateWasm, type WasmExports } from "./wasm-compute";
26
26
 
@@ -38,7 +38,7 @@ const STREAM_CHUNK_SIZE = 65536;
38
38
  export class VectorDB {
39
39
  private readonly memoryManager: MemoryManager;
40
40
  private readonly storage: StorageProvider;
41
- private readonly dimensions: number;
41
+ private readonly _dimensions: number;
42
42
  private readonly shouldNormalize: boolean;
43
43
  private wasmExports: WasmExports | null;
44
44
 
@@ -62,7 +62,7 @@ export class VectorDB {
62
62
  ) {
63
63
  this.memoryManager = memoryManager;
64
64
  this.storage = storage;
65
- this.dimensions = dimensions;
65
+ this._dimensions = dimensions;
66
66
  this.shouldNormalize = shouldNormalize;
67
67
  this.wasmExports = wasmExports;
68
68
  this.keyToSlot = keyToSlot;
@@ -76,8 +76,7 @@ export class VectorDB {
76
76
  static async open(options: OpenOptions): Promise<VectorDB>;
77
77
  static async open(options: OpenOptionsInternal): Promise<VectorDB>;
78
78
  static async open(options: OpenOptionsInternal): Promise<VectorDB> {
79
- const name = options.name ?? "default";
80
- const storage = options.storage ?? new OPFSStorageProvider(name);
79
+ const storage = options.storage ?? new InMemoryStorageProvider();
81
80
  const shouldNormalize = options.normalize !== false;
82
81
 
83
82
  // Load existing data from storage
@@ -123,6 +122,81 @@ export class VectorDB {
123
122
  return this.keyToSlot.size;
124
123
  }
125
124
 
125
+ /** Number of dimensions per vector */
126
+ get dimensions(): number {
127
+ return this._dimensions;
128
+ }
129
+
130
+ /**
131
+ * Check whether a key exists in the database.
132
+ * Uses the internal key-to-slot map for O(1) lookup.
133
+ */
134
+ has(key: string): boolean {
135
+ this.assertOpen();
136
+ return this.keyToSlot.has(key);
137
+ }
138
+
139
+ /**
140
+ * Delete an entry by key. Returns true if the key existed, false otherwise.
141
+ * Uses swap-and-pop to avoid gaps in the underlying vector array.
142
+ */
143
+ delete(key: string): boolean {
144
+ this.assertOpen();
145
+
146
+ const slot = this.keyToSlot.get(key);
147
+ if (slot === undefined) return false;
148
+
149
+ const lastSlot = this.memoryManager.vectorCount - 1;
150
+
151
+ if (slot !== lastSlot) {
152
+ // Move last vector into the deleted slot
153
+ const lastVector = new Float32Array(this.memoryManager.readVector(lastSlot));
154
+ this.memoryManager.writeVector(slot, lastVector);
155
+
156
+ // Update mappings for the moved key
157
+ const movedKey = this.slotToKey[lastSlot];
158
+ this.keyToSlot.set(movedKey, slot);
159
+ this.slotToKey[slot] = movedKey;
160
+ }
161
+
162
+ // Remove the deleted key and shrink
163
+ this.keyToSlot.delete(key);
164
+ this.slotToKey.length = lastSlot;
165
+ this.memoryManager.setVectorCount(lastSlot);
166
+
167
+ return true;
168
+ }
169
+
170
+ /**
171
+ * Returns an iterable of all keys in the database.
172
+ */
173
+ keys(): IterableIterator<string> {
174
+ this.assertOpen();
175
+ return this.keyToSlot.keys();
176
+ }
177
+
178
+ /**
179
+ * Returns an iterable of [key, value] pairs.
180
+ * Values are returned as plain number array copies.
181
+ */
182
+ entries(): IterableIterator<[string, number[]]> {
183
+ this.assertOpen();
184
+ const keyToSlot = this.keyToSlot;
185
+ const mm = this.memoryManager;
186
+ return (function* () {
187
+ for (const [key, slot] of keyToSlot) {
188
+ yield [key, Array.from(mm.readVector(slot))] as [string, number[]];
189
+ }
190
+ })();
191
+ }
192
+
193
+ /**
194
+ * Implements the iterable protocol. Same as entries().
195
+ */
196
+ [Symbol.iterator](): IterableIterator<[string, number[]]> {
197
+ return this.entries();
198
+ }
199
+
126
200
  /**
127
201
  * Set a key-value pair. If the key already exists, its vector is overwritten (last-write-wins).
128
202
  * The value is a number[] or Float32Array of length equal to the configured dimensions.
@@ -130,8 +204,8 @@ export class VectorDB {
130
204
  set(key: string, value: VectorInput, options?: SetOptions): void {
131
205
  this.assertOpen();
132
206
 
133
- if (value.length !== this.dimensions) {
134
- throw new Error(`Vector dimension mismatch: expected ${this.dimensions}, got ${value.length}`);
207
+ if (value.length !== this._dimensions) {
208
+ throw new Error(`Vector dimension mismatch: expected ${this._dimensions}, got ${value.length}`);
135
209
  }
136
210
 
137
211
  // Convert to Float32Array (also clones to avoid mutating caller's array)
@@ -215,8 +289,8 @@ export class VectorDB {
215
289
  return [];
216
290
  }
217
291
 
218
- if (value.length !== this.dimensions) {
219
- throw new Error(`Query vector dimension mismatch: expected ${this.dimensions}, got ${value.length}`);
292
+ if (value.length !== this._dimensions) {
293
+ throw new Error(`Query vector dimension mismatch: expected ${this._dimensions}, got ${value.length}`);
220
294
  }
221
295
 
222
296
  // Convert to Float32Array and optionally normalize the query vector
@@ -243,21 +317,21 @@ export class VectorDB {
243
317
  this.memoryManager.dbOffset,
244
318
  scoresOffset,
245
319
  totalVectors,
246
- this.dimensions,
320
+ this._dimensions,
247
321
  );
248
322
  } else {
249
323
  const queryView = new Float32Array(
250
324
  this.memoryManager.memory.buffer,
251
325
  this.memoryManager.queryOffset,
252
- this.dimensions,
326
+ this._dimensions,
253
327
  );
254
328
  const dbView = new Float32Array(
255
329
  this.memoryManager.memory.buffer,
256
330
  this.memoryManager.dbOffset,
257
- totalVectors * this.dimensions,
331
+ totalVectors * this._dimensions,
258
332
  );
259
333
  const scoresView = new Float32Array(this.memoryManager.memory.buffer, scoresOffset, totalVectors);
260
- searchAll(queryView, dbView, scoresView, totalVectors, this.dimensions);
334
+ searchAll(queryView, dbView, scoresView, totalVectors, this._dimensions);
261
335
  }
262
336
 
263
337
  // Read scores (make a copy so the buffer can be reused)
@@ -284,12 +358,12 @@ export class VectorDB {
284
358
  const totalVectors = this.memoryManager.vectorCount;
285
359
 
286
360
  // Serialize vectors from WASM memory
287
- const vectorBytes = new Uint8Array(totalVectors * this.dimensions * 4);
361
+ const vectorBytes = new Uint8Array(totalVectors * this._dimensions * 4);
288
362
  if (totalVectors > 0) {
289
363
  const src = new Uint8Array(
290
364
  this.memoryManager.memory.buffer,
291
365
  this.memoryManager.dbOffset,
292
- totalVectors * this.dimensions * 4,
366
+ totalVectors * this._dimensions * 4,
293
367
  );
294
368
  vectorBytes.set(src);
295
369
  }
@@ -336,7 +410,7 @@ export class VectorDB {
336
410
  this.assertOpen();
337
411
 
338
412
  const totalVectors = this.memoryManager.vectorCount;
339
- const vectorDataLen = totalVectors * this.dimensions * 4;
413
+ const vectorDataLen = totalVectors * this._dimensions * 4;
340
414
 
341
415
  // Encode keys (typically much smaller than vectors)
342
416
  const keysBytes = encodeLexicon(this.slotToKey);
@@ -347,7 +421,7 @@ export class VectorDB {
347
421
  const headerView = new DataView(header);
348
422
  headerView.setUint32(0, EXPORT_MAGIC, true);
349
423
  headerView.setUint32(4, EXPORT_VERSION, true);
350
- headerView.setUint32(8, this.dimensions, true);
424
+ headerView.setUint32(8, this._dimensions, true);
351
425
  headerView.setUint32(12, totalVectors, true);
352
426
  headerView.setUint32(16, vectorDataLen, true);
353
427
  headerView.setUint32(20, keysDataLen, true);
@@ -416,8 +490,8 @@ export class VectorDB {
416
490
  }
417
491
 
418
492
  const dimensions = headerView.getUint32(8, true);
419
- if (dimensions !== this.dimensions) {
420
- throw new Error(`Import dimension mismatch: expected ${this.dimensions}, got ${dimensions}`);
493
+ if (dimensions !== this._dimensions) {
494
+ throw new Error(`Import dimension mismatch: expected ${this._dimensions}, got ${dimensions}`);
421
495
  }
422
496
 
423
497
  const vectorCount = headerView.getUint32(12, true);