verso-db 0.1.5 → 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/CHANGELOG.md +13 -0
- package/README.md +13 -7
- package/dist/BinaryHeap.d.ts +11 -1
- package/dist/BinaryHeap.d.ts.map +1 -1
- package/dist/BinaryHeap.js +138 -0
- package/dist/BinaryHeap.js.map +1 -0
- package/dist/Collection.d.ts +30 -4
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Collection.js +1186 -0
- package/dist/Collection.js.map +1 -0
- package/dist/HNSWIndex.d.ts +59 -0
- package/dist/HNSWIndex.d.ts.map +1 -1
- package/dist/HNSWIndex.js +2818 -0
- package/dist/HNSWIndex.js.map +1 -0
- package/dist/MaxBinaryHeap.d.ts +2 -64
- package/dist/MaxBinaryHeap.d.ts.map +1 -1
- package/dist/MaxBinaryHeap.js +5 -0
- package/dist/MaxBinaryHeap.js.map +1 -0
- package/dist/SearchWorker.d.ts +57 -4
- package/dist/SearchWorker.d.ts.map +1 -1
- package/dist/SearchWorker.js +573 -0
- package/dist/SearchWorker.js.map +1 -0
- package/dist/VectorDB.d.ts.map +1 -1
- package/dist/VectorDB.js +246 -0
- package/dist/VectorDB.js.map +1 -0
- package/dist/WorkerPool.d.ts +32 -2
- package/dist/WorkerPool.d.ts.map +1 -1
- package/dist/WorkerPool.js +266 -0
- package/dist/WorkerPool.js.map +1 -0
- package/dist/backends/JsDistanceBackend.d.ts.map +1 -1
- package/dist/backends/JsDistanceBackend.js +163 -0
- package/dist/backends/JsDistanceBackend.js.map +1 -0
- package/dist/encoding/DeltaEncoder.d.ts +2 -2
- package/dist/encoding/DeltaEncoder.d.ts.map +1 -1
- package/dist/encoding/DeltaEncoder.js +199 -0
- package/dist/encoding/DeltaEncoder.js.map +1 -0
- package/dist/errors.js +97 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +61 -42
- package/dist/index.js.map +1 -9
- package/dist/presets.js +205 -0
- package/dist/presets.js.map +1 -0
- package/dist/quantization/ScalarQuantizer.d.ts +0 -34
- package/dist/quantization/ScalarQuantizer.d.ts.map +1 -1
- package/dist/quantization/ScalarQuantizer.js +346 -0
- package/dist/quantization/ScalarQuantizer.js.map +1 -0
- package/dist/storage/BatchWriter.js +351 -0
- package/dist/storage/BatchWriter.js.map +1 -0
- package/dist/storage/BunStorageBackend.d.ts +7 -3
- package/dist/storage/BunStorageBackend.d.ts.map +1 -1
- package/dist/storage/BunStorageBackend.js +182 -0
- package/dist/storage/BunStorageBackend.js.map +1 -0
- package/dist/storage/MemoryBackend.js +109 -0
- package/dist/storage/MemoryBackend.js.map +1 -0
- package/dist/storage/OPFSBackend.d.ts.map +1 -1
- package/dist/storage/OPFSBackend.js +325 -0
- package/dist/storage/OPFSBackend.js.map +1 -0
- package/dist/storage/StorageBackend.js +12 -0
- package/dist/storage/StorageBackend.js.map +1 -0
- package/dist/storage/WriteAheadLog.js +321 -0
- package/dist/storage/WriteAheadLog.js.map +1 -0
- package/dist/storage/createStorageBackend.d.ts +4 -0
- package/dist/storage/createStorageBackend.d.ts.map +1 -1
- package/dist/storage/createStorageBackend.js +119 -0
- package/dist/storage/createStorageBackend.js.map +1 -0
- package/{src/storage/index.ts → dist/storage/index.js} +7 -27
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/nodeFsRuntime.d.ts +14 -0
- package/dist/storage/nodeFsRuntime.d.ts.map +1 -0
- package/dist/storage/nodeFsRuntime.js +105 -0
- package/dist/storage/nodeFsRuntime.js.map +1 -0
- package/package.json +9 -7
- package/src/BinaryHeap.ts +0 -136
- package/src/Collection.ts +0 -1262
- package/src/HNSWIndex.ts +0 -2894
- package/src/MaxBinaryHeap.ts +0 -181
- package/src/SearchWorker.ts +0 -264
- package/src/VectorDB.ts +0 -319
- package/src/WorkerPool.ts +0 -222
- package/src/backends/JsDistanceBackend.ts +0 -171
- package/src/encoding/DeltaEncoder.ts +0 -236
- package/src/errors.ts +0 -110
- package/src/index.ts +0 -106
- package/src/presets.ts +0 -229
- package/src/quantization/ScalarQuantizer.ts +0 -487
- package/src/storage/BatchWriter.ts +0 -420
- package/src/storage/BunStorageBackend.ts +0 -199
- package/src/storage/MemoryBackend.ts +0 -122
- package/src/storage/OPFSBackend.ts +0 -348
- package/src/storage/StorageBackend.ts +0 -74
- package/src/storage/WriteAheadLog.ts +0 -379
- package/src/storage/createStorageBackend.ts +0 -137
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Delta Encoding with Varint for Neighbor Lists
|
|
3
|
-
*
|
|
4
|
-
* Implements delta-encoded neighbor lists as used by Qdrant for ~38% storage reduction.
|
|
5
|
-
* Neighbor IDs are sorted, then stored as deltas with variable-length encoding.
|
|
6
|
-
*
|
|
7
|
-
* Format:
|
|
8
|
-
* - First ID stored as full uint32
|
|
9
|
-
* - Subsequent IDs stored as varint deltas from previous ID
|
|
10
|
-
*
|
|
11
|
-
* Varint encoding (like Protocol Buffers):
|
|
12
|
-
* - Values 0-127: 1 byte
|
|
13
|
-
* - Values 128-16383: 2 bytes
|
|
14
|
-
* - Values 16384-2097151: 3 bytes
|
|
15
|
-
* - Values 2097152-268435455: 4 bytes
|
|
16
|
-
* - Larger: 5 bytes
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Encode an unsigned integer as a varint
|
|
21
|
-
* Returns the number of bytes written
|
|
22
|
-
*/
|
|
23
|
-
export function encodeVarint(value: number, buffer: Uint8Array, offset: number): number {
|
|
24
|
-
let v = value >>> 0; // Ensure unsigned
|
|
25
|
-
let bytesWritten = 0;
|
|
26
|
-
|
|
27
|
-
while (v >= 0x80) {
|
|
28
|
-
buffer[offset + bytesWritten] = (v & 0x7f) | 0x80;
|
|
29
|
-
v >>>= 7;
|
|
30
|
-
bytesWritten++;
|
|
31
|
-
}
|
|
32
|
-
buffer[offset + bytesWritten] = v;
|
|
33
|
-
return bytesWritten + 1;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Decode a varint from buffer
|
|
38
|
-
* Returns [value, bytesRead]
|
|
39
|
-
*/
|
|
40
|
-
export function decodeVarint(buffer: Uint8Array, offset: number): [number, number] {
|
|
41
|
-
let result = 0;
|
|
42
|
-
let shift = 0;
|
|
43
|
-
let bytesRead = 0;
|
|
44
|
-
|
|
45
|
-
while (offset + bytesRead < buffer.length) {
|
|
46
|
-
const byte = buffer[offset + bytesRead];
|
|
47
|
-
if (shift < 28) {
|
|
48
|
-
result |= (byte & 0x7f) << shift;
|
|
49
|
-
} else {
|
|
50
|
-
// At shift >= 28, use multiplication to avoid sign-bit corruption from << on int32
|
|
51
|
-
result = (result + (byte & 0x7f) * (2 ** shift)) >>> 0;
|
|
52
|
-
}
|
|
53
|
-
bytesRead++;
|
|
54
|
-
|
|
55
|
-
if ((byte & 0x80) === 0) {
|
|
56
|
-
return [result >>> 0, bytesRead];
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
shift += 7;
|
|
60
|
-
if (shift > 35) {
|
|
61
|
-
throw new Error('Varint too long');
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
throw new Error('Unexpected end of buffer');
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Calculate the number of bytes needed to encode a varint
|
|
70
|
-
*/
|
|
71
|
-
export function varintSize(value: number): number {
|
|
72
|
-
let v = value >>> 0;
|
|
73
|
-
let size = 1;
|
|
74
|
-
while (v >= 0x80) {
|
|
75
|
-
v >>>= 7;
|
|
76
|
-
size++;
|
|
77
|
-
}
|
|
78
|
-
return size;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Delta-encode a sorted array of neighbor IDs
|
|
83
|
-
* Returns the encoded buffer
|
|
84
|
-
*/
|
|
85
|
-
export function deltaEncodeNeighbors(neighbors: number[]): Uint8Array {
|
|
86
|
-
if (neighbors.length === 0) {
|
|
87
|
-
return new Uint8Array(0);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
for (let i = 0; i < neighbors.length; i++) {
|
|
91
|
-
const id = neighbors[i];
|
|
92
|
-
if (!Number.isInteger(id) || id < 0 || id > 0xFFFFFFFF) {
|
|
93
|
-
throw new Error(`Invalid neighbor ID at index ${i}: ${id}`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Sort neighbors for optimal delta encoding
|
|
98
|
-
const sorted = neighbors.slice().sort((a, b) => a - b);
|
|
99
|
-
|
|
100
|
-
// Calculate required buffer size
|
|
101
|
-
let size = 4; // First ID as uint32
|
|
102
|
-
let prev = sorted[0];
|
|
103
|
-
for (let i = 1; i < sorted.length; i++) {
|
|
104
|
-
const delta = sorted[i] - prev;
|
|
105
|
-
size += varintSize(delta);
|
|
106
|
-
prev = sorted[i];
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Encode
|
|
110
|
-
const buffer = new Uint8Array(size);
|
|
111
|
-
const view = new DataView(buffer.buffer);
|
|
112
|
-
|
|
113
|
-
// First ID as full uint32 (little-endian)
|
|
114
|
-
view.setUint32(0, sorted[0], true);
|
|
115
|
-
let offset = 4;
|
|
116
|
-
|
|
117
|
-
// Remaining as deltas
|
|
118
|
-
prev = sorted[0];
|
|
119
|
-
for (let i = 1; i < sorted.length; i++) {
|
|
120
|
-
const delta = sorted[i] - prev;
|
|
121
|
-
offset += encodeVarint(delta, buffer, offset);
|
|
122
|
-
prev = sorted[i];
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return buffer;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Decode a delta-encoded neighbor list
|
|
130
|
-
* Returns the original neighbor IDs (sorted)
|
|
131
|
-
*/
|
|
132
|
-
export function deltaDecodeNeighbors(buffer: Uint8Array, count: number): number[] {
|
|
133
|
-
if (count === 0 || buffer.length === 0) {
|
|
134
|
-
return [];
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
138
|
-
const neighbors = new Array<number>(count);
|
|
139
|
-
|
|
140
|
-
// First ID as full uint32
|
|
141
|
-
neighbors[0] = view.getUint32(0, true);
|
|
142
|
-
let offset = 4;
|
|
143
|
-
|
|
144
|
-
// Remaining as deltas
|
|
145
|
-
for (let i = 1; i < count; i++) {
|
|
146
|
-
const [delta, bytesRead] = decodeVarint(buffer, offset);
|
|
147
|
-
neighbors[i] = neighbors[i - 1] + delta;
|
|
148
|
-
offset += bytesRead;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return neighbors;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Calculate the encoded size for a neighbor list without actually encoding
|
|
156
|
-
* Useful for calculating total buffer size before serialization
|
|
157
|
-
*/
|
|
158
|
-
export function deltaEncodedSize(neighbors: number[]): number {
|
|
159
|
-
if (neighbors.length === 0) {
|
|
160
|
-
return 0;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
for (let i = 0; i < neighbors.length; i++) {
|
|
164
|
-
const id = neighbors[i];
|
|
165
|
-
if (!Number.isInteger(id) || id < 0 || id > 0xFFFFFFFF) {
|
|
166
|
-
throw new Error(`Invalid neighbor ID at index ${i}: ${id}`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const sorted = neighbors.slice().sort((a, b) => a - b);
|
|
171
|
-
|
|
172
|
-
let size = 4; // First ID as uint32
|
|
173
|
-
let prev = sorted[0];
|
|
174
|
-
for (let i = 1; i < sorted.length; i++) {
|
|
175
|
-
const delta = sorted[i] - prev;
|
|
176
|
-
size += varintSize(delta);
|
|
177
|
-
prev = sorted[i];
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return size;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Batch encode multiple neighbor lists efficiently
|
|
185
|
-
* Returns a single buffer with all encoded lists concatenated
|
|
186
|
-
* Also returns offsets for each list
|
|
187
|
-
*/
|
|
188
|
-
export function deltaEncodeBatch(neighborLists: number[][]): {
|
|
189
|
-
buffer: Uint8Array;
|
|
190
|
-
offsets: number[];
|
|
191
|
-
sizes: number[];
|
|
192
|
-
} {
|
|
193
|
-
// Calculate total size and individual sizes
|
|
194
|
-
const sizes = neighborLists.map(list => deltaEncodedSize(list));
|
|
195
|
-
const totalSize = sizes.reduce((a, b) => a + b, 0);
|
|
196
|
-
|
|
197
|
-
// Allocate single buffer
|
|
198
|
-
const buffer = new Uint8Array(totalSize);
|
|
199
|
-
const offsets: number[] = [];
|
|
200
|
-
|
|
201
|
-
let currentOffset = 0;
|
|
202
|
-
for (let i = 0; i < neighborLists.length; i++) {
|
|
203
|
-
offsets.push(currentOffset);
|
|
204
|
-
|
|
205
|
-
if (neighborLists[i].length > 0) {
|
|
206
|
-
const encoded = deltaEncodeNeighbors(neighborLists[i]);
|
|
207
|
-
buffer.set(encoded, currentOffset);
|
|
208
|
-
currentOffset += encoded.length;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return { buffer, offsets, sizes };
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Decode a batch of neighbor lists from a single buffer
|
|
217
|
-
*/
|
|
218
|
-
export function deltaDecodeBatch(
|
|
219
|
-
buffer: Uint8Array,
|
|
220
|
-
offsets: number[],
|
|
221
|
-
sizes: number[],
|
|
222
|
-
counts: number[]
|
|
223
|
-
): number[][] {
|
|
224
|
-
const results: number[][] = [];
|
|
225
|
-
|
|
226
|
-
for (let i = 0; i < offsets.length; i++) {
|
|
227
|
-
if (counts[i] === 0 || sizes[i] === 0) {
|
|
228
|
-
results.push([]);
|
|
229
|
-
} else {
|
|
230
|
-
const slice = buffer.subarray(offsets[i], offsets[i] + sizes[i]);
|
|
231
|
-
results.push(deltaDecodeNeighbors(slice, counts[i]));
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return results;
|
|
236
|
-
}
|
package/src/errors.ts
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base error class for all VectorDB errors
|
|
3
|
-
*/
|
|
4
|
-
export class VectorDBError extends Error {
|
|
5
|
-
readonly code: string;
|
|
6
|
-
|
|
7
|
-
constructor(message: string, code: string) {
|
|
8
|
-
super(message);
|
|
9
|
-
this.name = 'VectorDBError';
|
|
10
|
-
this.code = code;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Thrown when vector dimensions don't match expected dimensions
|
|
16
|
-
*/
|
|
17
|
-
export class DimensionMismatchError extends VectorDBError {
|
|
18
|
-
readonly expected: number;
|
|
19
|
-
readonly actual: number;
|
|
20
|
-
|
|
21
|
-
constructor(expected: number, actual: number, context?: string) {
|
|
22
|
-
const message = context
|
|
23
|
-
? `${context}: expected dimension ${expected}, got ${actual}`
|
|
24
|
-
: `Dimension mismatch: expected ${expected}, got ${actual}`;
|
|
25
|
-
super(message, 'DIMENSION_MISMATCH');
|
|
26
|
-
this.name = 'DimensionMismatchError';
|
|
27
|
-
this.expected = expected;
|
|
28
|
-
this.actual = actual;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Thrown when attempting to add a vector with an ID that already exists
|
|
34
|
-
*/
|
|
35
|
-
export class DuplicateVectorError extends VectorDBError {
|
|
36
|
-
readonly ids: string[];
|
|
37
|
-
|
|
38
|
-
constructor(ids: string[]) {
|
|
39
|
-
const message = ids.length === 1
|
|
40
|
-
? `Vector with ID '${ids[0]}' already exists`
|
|
41
|
-
: `Vectors with IDs already exist: ${ids.join(', ')}`;
|
|
42
|
-
super(message, 'DUPLICATE_VECTOR');
|
|
43
|
-
this.name = 'DuplicateVectorError';
|
|
44
|
-
this.ids = ids;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Thrown when a requested collection does not exist
|
|
50
|
-
*/
|
|
51
|
-
export class CollectionNotFoundError extends VectorDBError {
|
|
52
|
-
readonly collectionName: string;
|
|
53
|
-
|
|
54
|
-
constructor(collectionName: string) {
|
|
55
|
-
super(`Collection '${collectionName}' does not exist`, 'COLLECTION_NOT_FOUND');
|
|
56
|
-
this.name = 'CollectionNotFoundError';
|
|
57
|
-
this.collectionName = collectionName;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Thrown when attempting to create a collection that already exists
|
|
63
|
-
*/
|
|
64
|
-
export class CollectionExistsError extends VectorDBError {
|
|
65
|
-
readonly collectionName: string;
|
|
66
|
-
|
|
67
|
-
constructor(collectionName: string) {
|
|
68
|
-
super(`Collection '${collectionName}' already exists`, 'COLLECTION_EXISTS');
|
|
69
|
-
this.name = 'CollectionExistsError';
|
|
70
|
-
this.collectionName = collectionName;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Thrown when a storage operation fails
|
|
76
|
-
*/
|
|
77
|
-
export class StorageError extends VectorDBError {
|
|
78
|
-
readonly operation: string;
|
|
79
|
-
readonly path?: string;
|
|
80
|
-
|
|
81
|
-
constructor(operation: string, message: string, path?: string) {
|
|
82
|
-
super(`Storage ${operation} failed: ${message}`, 'STORAGE_ERROR');
|
|
83
|
-
this.name = 'StorageError';
|
|
84
|
-
this.operation = operation;
|
|
85
|
-
this.path = path;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Thrown when quantization operations fail
|
|
91
|
-
*/
|
|
92
|
-
export class QuantizationError extends VectorDBError {
|
|
93
|
-
constructor(message: string) {
|
|
94
|
-
super(message, 'QUANTIZATION_ERROR');
|
|
95
|
-
this.name = 'QuantizationError';
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Thrown when a vector is not found in the index
|
|
101
|
-
*/
|
|
102
|
-
export class VectorNotFoundError extends VectorDBError {
|
|
103
|
-
readonly vectorId: string | number;
|
|
104
|
-
|
|
105
|
-
constructor(vectorId: string | number) {
|
|
106
|
-
super(`Vector '${vectorId}' not found`, 'VECTOR_NOT_FOUND');
|
|
107
|
-
this.name = 'VectorNotFoundError';
|
|
108
|
-
this.vectorId = vectorId;
|
|
109
|
-
}
|
|
110
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* verso-db - High-performance vector search with HNSW indexing
|
|
3
|
-
*
|
|
4
|
-
* Features:
|
|
5
|
-
* - HNSW algorithm for approximate nearest neighbor search
|
|
6
|
-
* - Multiple distance metrics: cosine, euclidean, dot product
|
|
7
|
-
* - Int8 scalar quantization for 4x memory reduction
|
|
8
|
-
* - Batch query support for improved throughput
|
|
9
|
-
* - Parameter presets for different use cases
|
|
10
|
-
* - Multi-platform: Bun (file system) and Browser (OPFS)
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```typescript
|
|
14
|
-
* import { VectorDB, getRecommendedPreset } from 'verso-db';
|
|
15
|
-
*
|
|
16
|
-
* const db = new VectorDB({ storagePath: './vectors' });
|
|
17
|
-
* const preset = getRecommendedPreset(768);
|
|
18
|
-
*
|
|
19
|
-
* const collection = await db.createCollection('my-vectors', {
|
|
20
|
-
* dimension: 768,
|
|
21
|
-
* metric: 'cosine',
|
|
22
|
-
* M: preset.M,
|
|
23
|
-
* efConstruction: preset.efConstruction
|
|
24
|
-
* });
|
|
25
|
-
*
|
|
26
|
-
* await collection.add({
|
|
27
|
-
* ids: ['doc1', 'doc2'],
|
|
28
|
-
* vectors: [new Float32Array(768), new Float32Array(768)]
|
|
29
|
-
* });
|
|
30
|
-
*
|
|
31
|
-
* const results = await collection.query({
|
|
32
|
-
* queryVector: new Float32Array(768),
|
|
33
|
-
* k: 10,
|
|
34
|
-
* efSearch: preset.efSearch
|
|
35
|
-
* });
|
|
36
|
-
* ```
|
|
37
|
-
*
|
|
38
|
-
* @packageDocumentation
|
|
39
|
-
* @module verso-db
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
43
|
-
|
|
44
|
-
// Core
|
|
45
|
-
export { VectorDB } from './VectorDB';
|
|
46
|
-
export type { VectorDBConfig, CollectionConfig } from './VectorDB';
|
|
47
|
-
export { Collection } from './Collection';
|
|
48
|
-
export type { AddConfig, QueryConfig, QueryResult } from './Collection';
|
|
49
|
-
export { HNSWIndex } from './HNSWIndex';
|
|
50
|
-
export type { DistanceMetric } from './HNSWIndex';
|
|
51
|
-
|
|
52
|
-
// Parameter presets
|
|
53
|
-
export type { HNSWPreset } from './presets';
|
|
54
|
-
export {
|
|
55
|
-
PRESET_LOW_DIM,
|
|
56
|
-
PRESET_MEDIUM_DIM,
|
|
57
|
-
PRESET_HIGH_DIM,
|
|
58
|
-
PRESET_VERY_HIGH_DIM,
|
|
59
|
-
PRESET_SMALL_DATASET,
|
|
60
|
-
PRESET_LARGE_DATASET,
|
|
61
|
-
PRESET_MAX_RECALL,
|
|
62
|
-
PRESET_LOW_LATENCY,
|
|
63
|
-
PRESETS,
|
|
64
|
-
getRecommendedPreset,
|
|
65
|
-
getPreset,
|
|
66
|
-
getRAGPreset
|
|
67
|
-
} from './presets';
|
|
68
|
-
|
|
69
|
-
// Quantization
|
|
70
|
-
export { ScalarQuantizer, QuantizedVectorStore } from './quantization/ScalarQuantizer';
|
|
71
|
-
export type { QuantizationParams } from './quantization/ScalarQuantizer';
|
|
72
|
-
|
|
73
|
-
// Storage backends
|
|
74
|
-
export type { StorageBackend, StorageOptions } from './storage/StorageBackend';
|
|
75
|
-
export { BunStorageBackend } from './storage/BunStorageBackend';
|
|
76
|
-
export { MemoryBackend } from './storage/MemoryBackend';
|
|
77
|
-
export { OPFSBackend } from './storage/OPFSBackend';
|
|
78
|
-
export {
|
|
79
|
-
createStorageBackend,
|
|
80
|
-
getRecommendedStorageType,
|
|
81
|
-
isStorageTypeAvailable,
|
|
82
|
-
type StorageType,
|
|
83
|
-
type CreateStorageOptions,
|
|
84
|
-
} from './storage/createStorageBackend';
|
|
85
|
-
|
|
86
|
-
// Write-ahead log and batch writer
|
|
87
|
-
export { WriteAheadLog, WALOperationType } from './storage/WriteAheadLog';
|
|
88
|
-
export type { WALEntry } from './storage/WriteAheadLog';
|
|
89
|
-
export { BatchWriter, createBatchWriter } from './storage/BatchWriter';
|
|
90
|
-
export type { BatchWriterOptions } from './storage/BatchWriter';
|
|
91
|
-
|
|
92
|
-
// Parallel query processing
|
|
93
|
-
export { WorkerPool } from './WorkerPool';
|
|
94
|
-
export { WorkerSearchState } from './SearchWorker';
|
|
95
|
-
|
|
96
|
-
// Error classes
|
|
97
|
-
export {
|
|
98
|
-
VectorDBError,
|
|
99
|
-
DimensionMismatchError,
|
|
100
|
-
DuplicateVectorError,
|
|
101
|
-
CollectionNotFoundError,
|
|
102
|
-
CollectionExistsError,
|
|
103
|
-
StorageError,
|
|
104
|
-
QuantizationError,
|
|
105
|
-
VectorNotFoundError,
|
|
106
|
-
} from './errors';
|
package/src/presets.ts
DELETED
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HNSW Parameter Presets
|
|
3
|
-
*
|
|
4
|
-
* These presets are optimized based on extensive benchmarking to achieve
|
|
5
|
-
* the target recall@10 >= 95% for different dataset sizes and dimensions.
|
|
6
|
-
*
|
|
7
|
-
* Key parameters:
|
|
8
|
-
* - M: Maximum connections per node (higher = better recall, more memory)
|
|
9
|
-
* - efConstruction: Beam width during index building (higher = better quality, slower build)
|
|
10
|
-
* - efSearch: Beam width during search (higher = better recall, slower search)
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export interface HNSWPreset {
|
|
14
|
-
name: string;
|
|
15
|
-
description: string;
|
|
16
|
-
M: number;
|
|
17
|
-
efConstruction: number;
|
|
18
|
-
efSearch: number;
|
|
19
|
-
expectedRecall: number;
|
|
20
|
-
targetDimensions: string;
|
|
21
|
-
targetDatasetSize: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Preset for low-dimensional vectors (128D or less)
|
|
26
|
-
* Suitable for: Image features, word2vec, GloVe embeddings
|
|
27
|
-
*/
|
|
28
|
-
export const PRESET_LOW_DIM: Readonly<HNSWPreset> = Object.freeze({
|
|
29
|
-
name: 'low-dim',
|
|
30
|
-
description: 'Optimized for low-dimensional vectors (<=128D)',
|
|
31
|
-
M: 16,
|
|
32
|
-
efConstruction: 200,
|
|
33
|
-
efSearch: 100,
|
|
34
|
-
expectedRecall: 0.99,
|
|
35
|
-
targetDimensions: '<=128',
|
|
36
|
-
targetDatasetSize: '1K-100K',
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Preset for medium-dimensional vectors (256-512D)
|
|
41
|
-
* Suitable for: Sentence embeddings, smaller transformer outputs
|
|
42
|
-
*/
|
|
43
|
-
export const PRESET_MEDIUM_DIM: Readonly<HNSWPreset> = Object.freeze({
|
|
44
|
-
name: 'medium-dim',
|
|
45
|
-
description: 'Optimized for medium-dimensional vectors (129-512D)',
|
|
46
|
-
M: 24,
|
|
47
|
-
efConstruction: 200,
|
|
48
|
-
efSearch: 150,
|
|
49
|
-
expectedRecall: 0.97,
|
|
50
|
-
targetDimensions: '129-512',
|
|
51
|
-
targetDatasetSize: '1K-100K',
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Preset for high-dimensional vectors (768D+)
|
|
56
|
-
* Suitable for: BERT, GPT embeddings, Cohere, OpenAI embeddings
|
|
57
|
-
* This is the recommended preset for RAG applications
|
|
58
|
-
*
|
|
59
|
-
* Benchmarked on Cohere Wikipedia 1024D (495K vectors):
|
|
60
|
-
* - efSearch=128: 99.2% recall, 168 QPS, 10.72ms P99
|
|
61
|
-
*/
|
|
62
|
-
export const PRESET_HIGH_DIM: Readonly<HNSWPreset> = Object.freeze({
|
|
63
|
-
name: 'high-dim',
|
|
64
|
-
description: 'Optimized for high-dimensional vectors (768D+)',
|
|
65
|
-
M: 32,
|
|
66
|
-
efConstruction: 200,
|
|
67
|
-
efSearch: 128,
|
|
68
|
-
expectedRecall: 0.99,
|
|
69
|
-
targetDimensions: '>=768',
|
|
70
|
-
targetDatasetSize: '1K-500K',
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Preset for very high-dimensional vectors (1536D+)
|
|
75
|
-
* Suitable for: OpenAI text-embedding-ada-002, text-embedding-3-large
|
|
76
|
-
*
|
|
77
|
-
* Scaled from PRESET_HIGH_DIM benchmarks (higher M for higher dimensions)
|
|
78
|
-
*/
|
|
79
|
-
export const PRESET_VERY_HIGH_DIM: Readonly<HNSWPreset> = Object.freeze({
|
|
80
|
-
name: 'very-high-dim',
|
|
81
|
-
description: 'Optimized for very high-dimensional vectors (1536D+)',
|
|
82
|
-
M: 48,
|
|
83
|
-
efConstruction: 300,
|
|
84
|
-
efSearch: 150,
|
|
85
|
-
expectedRecall: 0.99,
|
|
86
|
-
targetDimensions: '>=1536',
|
|
87
|
-
targetDatasetSize: '1K-500K',
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Preset for small datasets (<10K vectors)
|
|
92
|
-
* Prioritizes recall over speed since brute-force is viable
|
|
93
|
-
*/
|
|
94
|
-
export const PRESET_SMALL_DATASET: Readonly<HNSWPreset> = Object.freeze({
|
|
95
|
-
name: 'small-dataset',
|
|
96
|
-
description: 'Optimized for small datasets (<10K vectors)',
|
|
97
|
-
M: 16,
|
|
98
|
-
efConstruction: 200,
|
|
99
|
-
efSearch: 200,
|
|
100
|
-
expectedRecall: 0.99,
|
|
101
|
-
targetDimensions: 'any',
|
|
102
|
-
targetDatasetSize: '<10K',
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Preset for large datasets (100K-1M vectors)
|
|
107
|
-
* Balances recall with build time and memory
|
|
108
|
-
*
|
|
109
|
-
* Benchmarked on Cohere Wikipedia 1024D (495K vectors):
|
|
110
|
-
* - efSearch=128: 99.2% recall, 168 QPS
|
|
111
|
-
*/
|
|
112
|
-
export const PRESET_LARGE_DATASET: Readonly<HNSWPreset> = Object.freeze({
|
|
113
|
-
name: 'large-dataset',
|
|
114
|
-
description: 'Optimized for large datasets (100K-1M vectors)',
|
|
115
|
-
M: 32,
|
|
116
|
-
efConstruction: 200,
|
|
117
|
-
efSearch: 128,
|
|
118
|
-
expectedRecall: 0.99,
|
|
119
|
-
targetDimensions: 'any',
|
|
120
|
-
targetDatasetSize: '100K-1M',
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Preset for maximum recall (prioritizes accuracy over speed)
|
|
125
|
-
* Use when recall is critical and latency is acceptable
|
|
126
|
-
*/
|
|
127
|
-
export const PRESET_MAX_RECALL: Readonly<HNSWPreset> = Object.freeze({
|
|
128
|
-
name: 'max-recall',
|
|
129
|
-
description: 'Maximum recall configuration',
|
|
130
|
-
M: 48,
|
|
131
|
-
efConstruction: 500,
|
|
132
|
-
efSearch: 400,
|
|
133
|
-
expectedRecall: 0.99,
|
|
134
|
-
targetDimensions: 'any',
|
|
135
|
-
targetDatasetSize: 'any',
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Preset for minimum latency (prioritizes speed over recall)
|
|
140
|
-
* Use when latency is critical and 90% recall is acceptable
|
|
141
|
-
*/
|
|
142
|
-
export const PRESET_LOW_LATENCY: Readonly<HNSWPreset> = Object.freeze({
|
|
143
|
-
name: 'low-latency',
|
|
144
|
-
description: 'Minimum latency configuration (90% recall)',
|
|
145
|
-
M: 12,
|
|
146
|
-
efConstruction: 100,
|
|
147
|
-
efSearch: 50,
|
|
148
|
-
expectedRecall: 0.90,
|
|
149
|
-
targetDimensions: 'any',
|
|
150
|
-
targetDatasetSize: 'any',
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* All available presets
|
|
155
|
-
*/
|
|
156
|
-
export const PRESETS: Readonly<Record<string, Readonly<HNSWPreset>>> = Object.freeze({
|
|
157
|
-
'low-dim': PRESET_LOW_DIM,
|
|
158
|
-
'medium-dim': PRESET_MEDIUM_DIM,
|
|
159
|
-
'high-dim': PRESET_HIGH_DIM,
|
|
160
|
-
'very-high-dim': PRESET_VERY_HIGH_DIM,
|
|
161
|
-
'small-dataset': PRESET_SMALL_DATASET,
|
|
162
|
-
'large-dataset': PRESET_LARGE_DATASET,
|
|
163
|
-
'max-recall': PRESET_MAX_RECALL,
|
|
164
|
-
'low-latency': PRESET_LOW_LATENCY,
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Get recommended preset based on dimension and dataset size
|
|
169
|
-
*
|
|
170
|
-
* For high-dimensional vectors (768D+), dimension takes priority over dataset size
|
|
171
|
-
* because recall degrades significantly without higher M values.
|
|
172
|
-
*/
|
|
173
|
-
export function getRecommendedPreset(dimension: number, datasetSize?: number): HNSWPreset {
|
|
174
|
-
// For high-dimensional vectors, always use dimension-based presets
|
|
175
|
-
// (dimension matters more than dataset size for recall)
|
|
176
|
-
// Note: Check 1536 BEFORE 768 since 1536 >= 768 would match first
|
|
177
|
-
if (dimension >= 1536) return PRESET_VERY_HIGH_DIM;
|
|
178
|
-
if (dimension >= 768) return PRESET_HIGH_DIM;
|
|
179
|
-
|
|
180
|
-
// For lower dimensions, consider dataset size
|
|
181
|
-
if (datasetSize !== undefined) {
|
|
182
|
-
if (datasetSize < 10000) return PRESET_SMALL_DATASET;
|
|
183
|
-
if (datasetSize > 100000) return PRESET_LARGE_DATASET;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Dimension-based selection for medium dimensions
|
|
187
|
-
if (dimension <= 128) return PRESET_LOW_DIM;
|
|
188
|
-
if (dimension <= 512) return PRESET_MEDIUM_DIM;
|
|
189
|
-
|
|
190
|
-
return PRESET_HIGH_DIM;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Get preset by name
|
|
195
|
-
*/
|
|
196
|
-
export function getPreset(name: string): HNSWPreset | undefined {
|
|
197
|
-
return PRESETS[name];
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* RAG-specific preset recommendation
|
|
202
|
-
* For typical RAG applications using popular embedding models
|
|
203
|
-
*/
|
|
204
|
-
export function getRAGPreset(embeddingModel: string): HNSWPreset {
|
|
205
|
-
const model = embeddingModel.toLowerCase();
|
|
206
|
-
|
|
207
|
-
// OpenAI models
|
|
208
|
-
if (model.includes('ada-002') || model.includes('text-embedding-3')) {
|
|
209
|
-
return PRESET_VERY_HIGH_DIM;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Cohere models
|
|
213
|
-
if (model.includes('cohere') || model.includes('embed-')) {
|
|
214
|
-
return PRESET_HIGH_DIM;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// BERT/Sentence Transformers
|
|
218
|
-
if (model.includes('bert') || model.includes('minilm') || model.includes('mpnet')) {
|
|
219
|
-
return PRESET_HIGH_DIM;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// E5 models
|
|
223
|
-
if (model.includes('e5-')) {
|
|
224
|
-
return model.includes('large') ? PRESET_HIGH_DIM : PRESET_MEDIUM_DIM;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Default to high-dim for unknown models (most modern embeddings are 768D+)
|
|
228
|
-
return PRESET_HIGH_DIM;
|
|
229
|
-
}
|