eigen-db 4.3.0 → 5.0.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 +8 -0
- package/README.md +74 -23
- package/dist/compute.d.ts +20 -0
- package/dist/eigen-db.js +228 -173
- package/dist/eigen-db.js.map +1 -1
- package/dist/eigen-db.umd.cjs +1 -1
- package/dist/eigen-db.umd.cjs.map +1 -1
- package/dist/errors.d.ts +7 -0
- package/dist/index.d.ts +12 -0
- package/dist/lexicon.d.ts +28 -0
- package/dist/memory-manager.d.ts +68 -0
- package/dist/result-set.d.ts +46 -0
- package/dist/simd-binary.d.ts +1 -0
- package/dist/storage.d.ts +38 -0
- package/dist/types.d.ts +48 -0
- package/dist/vector-db.d.ts +131 -0
- package/dist/wasm-compute.d.ts +13 -0
- package/package.json +4 -4
- package/src/lib/__tests__/result-set.test.ts +146 -27
- package/src/lib/__tests__/vector-db.test.ts +476 -4
- package/src/lib/result-set.ts +55 -24
- package/src/lib/types.ts +5 -1
- package/src/lib/vector-db.ts +99 -20
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VectorDB — Key-Value Vector Database
|
|
3
|
+
*
|
|
4
|
+
* Decoupled from embedding providers. Users pass pre-computed vectors
|
|
5
|
+
* as number arrays (or Float32Array) with string keys.
|
|
6
|
+
*
|
|
7
|
+
* Supports:
|
|
8
|
+
* - set/get/setMany/getMany for key-value CRUD
|
|
9
|
+
* - query for similarity search (dot product on normalized vectors)
|
|
10
|
+
* - flush to persist, close to flush+release, clear to wipe
|
|
11
|
+
* - Streaming export/import using Web Streams API
|
|
12
|
+
* - Last-write-wins semantics for duplicate keys (append-only storage)
|
|
13
|
+
*/
|
|
14
|
+
import type { ResultItem } from "./result-set";
|
|
15
|
+
import type { OpenOptions, OpenOptionsInternal, QueryOptions, SetOptions, VectorInput } from "./types";
|
|
16
|
+
export declare class VectorDB {
|
|
17
|
+
private readonly memoryManager;
|
|
18
|
+
private readonly storage;
|
|
19
|
+
private readonly _dimensions;
|
|
20
|
+
private readonly shouldNormalize;
|
|
21
|
+
private wasmExports;
|
|
22
|
+
/** Maps key to its slot index in the vector array */
|
|
23
|
+
private keyToSlot;
|
|
24
|
+
/** Maps slot index back to its key */
|
|
25
|
+
private slotToKey;
|
|
26
|
+
/** Whether this instance has been closed */
|
|
27
|
+
private closed;
|
|
28
|
+
private constructor();
|
|
29
|
+
/**
|
|
30
|
+
* Opens a VectorDB instance.
|
|
31
|
+
* Loads existing data from storage into WASM memory.
|
|
32
|
+
*/
|
|
33
|
+
static open(options: OpenOptions): Promise<VectorDB>;
|
|
34
|
+
static open(options: OpenOptionsInternal): Promise<VectorDB>;
|
|
35
|
+
/** Total number of key-value pairs in the database */
|
|
36
|
+
get size(): number;
|
|
37
|
+
/** Number of dimensions per vector */
|
|
38
|
+
get dimensions(): number;
|
|
39
|
+
/**
|
|
40
|
+
* Check whether a key exists in the database.
|
|
41
|
+
* Uses the internal key-to-slot map for O(1) lookup.
|
|
42
|
+
*/
|
|
43
|
+
has(key: string): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Delete an entry by key. Returns true if the key existed, false otherwise.
|
|
46
|
+
* Uses swap-and-pop to avoid gaps in the underlying vector array.
|
|
47
|
+
*/
|
|
48
|
+
delete(key: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Returns an iterable of all keys in the database.
|
|
51
|
+
*/
|
|
52
|
+
keys(): IterableIterator<string>;
|
|
53
|
+
/**
|
|
54
|
+
* Returns an iterable of [key, value] pairs.
|
|
55
|
+
* Values are returned as plain number array copies.
|
|
56
|
+
*/
|
|
57
|
+
entries(): IterableIterator<[string, number[]]>;
|
|
58
|
+
/**
|
|
59
|
+
* Implements the iterable protocol. Same as entries().
|
|
60
|
+
*/
|
|
61
|
+
[Symbol.iterator](): IterableIterator<[string, number[]]>;
|
|
62
|
+
/**
|
|
63
|
+
* Set a key-value pair. If the key already exists, its vector is overwritten (last-write-wins).
|
|
64
|
+
* The value is a number[] or Float32Array of length equal to the configured dimensions.
|
|
65
|
+
*/
|
|
66
|
+
set(key: string, value: VectorInput, options?: SetOptions): void;
|
|
67
|
+
/**
|
|
68
|
+
* Get the stored vector for a key. Returns undefined if the key does not exist.
|
|
69
|
+
* Returns a copy of the stored vector as a plain number array.
|
|
70
|
+
*/
|
|
71
|
+
get(key: string): number[] | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Set multiple key-value pairs at once. Last-write-wins applies within the batch.
|
|
74
|
+
*/
|
|
75
|
+
setMany(entries: [string, VectorInput][]): void;
|
|
76
|
+
/**
|
|
77
|
+
* Get vectors for multiple keys. Returns undefined for keys that don't exist.
|
|
78
|
+
*/
|
|
79
|
+
getMany(keys: string[]): (number[] | undefined)[];
|
|
80
|
+
/**
|
|
81
|
+
* Search for the most similar vectors to the given query vector.
|
|
82
|
+
*
|
|
83
|
+
* Default: returns a plain ResultItem[] sorted by descending similarity.
|
|
84
|
+
* With `{ iterable: true }`: returns a lazy Iterable<ResultItem> where keys
|
|
85
|
+
* are resolved only as each item is consumed.
|
|
86
|
+
*
|
|
87
|
+
* Similarity is the dot product of query and stored vectors. With
|
|
88
|
+
* normalization (default), this equals cosine similarity: 1 = identical,
|
|
89
|
+
* -1 = opposite.
|
|
90
|
+
*/
|
|
91
|
+
query(value: VectorInput, options: QueryOptions & {
|
|
92
|
+
iterable: true;
|
|
93
|
+
}): Iterable<ResultItem>;
|
|
94
|
+
query(value: VectorInput, options?: QueryOptions): ResultItem[];
|
|
95
|
+
/**
|
|
96
|
+
* Persist the current in-memory state to storage.
|
|
97
|
+
*/
|
|
98
|
+
flush(): Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* Flush data to storage and release the instance.
|
|
101
|
+
* The instance cannot be used after close.
|
|
102
|
+
*/
|
|
103
|
+
close(): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Clear all data from the database and storage.
|
|
106
|
+
*/
|
|
107
|
+
clear(): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* Export the entire database as a ReadableStream of binary chunks.
|
|
110
|
+
*
|
|
111
|
+
* Format: [Header 24 bytes][Vector data][Keys data]
|
|
112
|
+
* Header: magic(4) + version(4) + dimensions(4) + vectorCount(4) + vectorDataLen(4) + keysDataLen(4)
|
|
113
|
+
*
|
|
114
|
+
* Vectors are streamed in 64KB chunks from WASM memory to avoid large
|
|
115
|
+
* heap allocations.
|
|
116
|
+
*/
|
|
117
|
+
export(): Promise<ReadableStream<Uint8Array>>;
|
|
118
|
+
/**
|
|
119
|
+
* Import data from a ReadableStream, replacing all existing data.
|
|
120
|
+
* Performs a dimension check against the configured dimensions.
|
|
121
|
+
*
|
|
122
|
+
* Vectors are streamed directly into WASM memory in chunks to avoid
|
|
123
|
+
* large heap allocations.
|
|
124
|
+
*/
|
|
125
|
+
import(stream: ReadableStream<Uint8Array>): Promise<void>;
|
|
126
|
+
/**
|
|
127
|
+
* Normalize a vector using WASM (if available) or JS fallback.
|
|
128
|
+
*/
|
|
129
|
+
private normalizeVector;
|
|
130
|
+
private assertOpen;
|
|
131
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WASM SIMD compute layer.
|
|
3
|
+
* Compiles the hand-written WAT module and provides typed wrappers
|
|
4
|
+
* that operate on shared WebAssembly.Memory.
|
|
5
|
+
*/
|
|
6
|
+
export interface WasmExports {
|
|
7
|
+
normalize(ptr: number, dimensions: number): void;
|
|
8
|
+
search_all(queryPtr: number, dbPtr: number, scoresPtr: number, dbSize: number, dimensions: number): void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Instantiates a WASM module with the given memory and returns typed exports.
|
|
12
|
+
*/
|
|
13
|
+
export declare function instantiateWasm(wasmBinary: Uint8Array, memory: WebAssembly.Memory): Promise<WasmExports>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eigen-db",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -12,16 +12,16 @@
|
|
|
12
12
|
"module": "./dist/eigen-db.js",
|
|
13
13
|
"exports": {
|
|
14
14
|
".": {
|
|
15
|
-
"types": "./
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
16
|
"import": "./dist/eigen-db.js",
|
|
17
17
|
"require": "./dist/eigen-db.umd.cjs"
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
|
-
"types": "./
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
21
|
"scripts": {
|
|
22
22
|
"dev": "vite",
|
|
23
23
|
"compile-wat": "tsx scripts/compile-wat.ts",
|
|
24
|
-
"build": "npm run compile-wat && tsc && vite build",
|
|
24
|
+
"build": "npm run compile-wat && tsc && vite build && tsc -p tsconfig.build.json",
|
|
25
25
|
"preview": "vite preview",
|
|
26
26
|
"test": "vitest run",
|
|
27
27
|
"test:watch": "vitest",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { iterableResults,
|
|
2
|
+
import { iterableResults, queryResults } from "../result-set";
|
|
3
3
|
|
|
4
|
-
describe("
|
|
4
|
+
describe("queryResults", () => {
|
|
5
5
|
const keys = ["apple", "banana", "cherry", "date", "elderberry"];
|
|
6
6
|
const resolveKey = (index: number) => keys[index];
|
|
7
7
|
|
|
8
|
-
it("sorts results by descending similarity", () => {
|
|
8
|
+
it("sorts results by descending similarity (default order)", () => {
|
|
9
9
|
const scores = new Float32Array([0.3, 0.9, 0.1, 0.7, 0.5]);
|
|
10
|
-
const results =
|
|
10
|
+
const results = queryResults(scores, resolveKey, { limit: Infinity, order: "descend" });
|
|
11
11
|
|
|
12
12
|
expect(results).toHaveLength(5);
|
|
13
13
|
expect(results[0].key).toBe("banana");
|
|
@@ -19,9 +19,22 @@ describe("topKResults", () => {
|
|
|
19
19
|
expect(results[4].key).toBe("cherry");
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
it("
|
|
22
|
+
it("sorts results by ascending similarity", () => {
|
|
23
|
+
const scores = new Float32Array([0.3, 0.9, 0.1, 0.7, 0.5]);
|
|
24
|
+
const results = queryResults(scores, resolveKey, { limit: Infinity, order: "ascend" });
|
|
25
|
+
|
|
26
|
+
expect(results).toHaveLength(5);
|
|
27
|
+
expect(results[0].key).toBe("cherry");
|
|
28
|
+
expect(results[0].similarity).toBeCloseTo(0.1, 4);
|
|
29
|
+
expect(results[1].key).toBe("apple");
|
|
30
|
+
expect(results[1].similarity).toBeCloseTo(0.3, 4);
|
|
31
|
+
expect(results[4].key).toBe("banana");
|
|
32
|
+
expect(results[4].similarity).toBeCloseTo(0.9, 4);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("respects limit", () => {
|
|
23
36
|
const scores = new Float32Array([0.3, 0.9, 0.1, 0.7, 0.5]);
|
|
24
|
-
const results =
|
|
37
|
+
const results = queryResults(scores, resolveKey, { limit: 3, order: "descend" });
|
|
25
38
|
|
|
26
39
|
expect(results).toHaveLength(3);
|
|
27
40
|
expect(results[0].key).toBe("banana");
|
|
@@ -31,20 +44,19 @@ describe("topKResults", () => {
|
|
|
31
44
|
|
|
32
45
|
it("handles empty scores", () => {
|
|
33
46
|
const scores = new Float32Array(0);
|
|
34
|
-
const results =
|
|
47
|
+
const results = queryResults(scores, resolveKey, { limit: 10, order: "descend" });
|
|
35
48
|
expect(results).toEqual([]);
|
|
36
49
|
});
|
|
37
50
|
|
|
38
|
-
it("handles
|
|
51
|
+
it("handles limit larger than result count", () => {
|
|
39
52
|
const scores = new Float32Array([0.5, 0.8]);
|
|
40
|
-
const results =
|
|
53
|
+
const results = queryResults(scores, resolveKey, { limit: 100, order: "descend" });
|
|
41
54
|
expect(results).toHaveLength(2);
|
|
42
55
|
});
|
|
43
56
|
|
|
44
57
|
it("filters results by minSimilarity", () => {
|
|
45
58
|
const scores = new Float32Array([0.3, 0.9, 0.1, 0.7, 0.5]);
|
|
46
|
-
|
|
47
|
-
const results = topKResults(scores, resolveKey, 5, 0.5);
|
|
59
|
+
const results = queryResults(scores, resolveKey, { limit: Infinity, order: "descend", minSimilarity: 0.5 });
|
|
48
60
|
|
|
49
61
|
expect(results).toHaveLength(3);
|
|
50
62
|
expect(results[0].key).toBe("banana");
|
|
@@ -57,15 +69,81 @@ describe("topKResults", () => {
|
|
|
57
69
|
|
|
58
70
|
it("minSimilarity is inclusive", () => {
|
|
59
71
|
const scores = new Float32Array([0.5]);
|
|
60
|
-
|
|
61
|
-
const results = topKResults(scores, resolveKey, 10, 0.5);
|
|
72
|
+
const results = queryResults(scores, resolveKey, { limit: 10, order: "descend", minSimilarity: 0.5 });
|
|
62
73
|
expect(results).toHaveLength(1);
|
|
63
74
|
});
|
|
64
75
|
|
|
65
|
-
it("
|
|
76
|
+
it("filters results by maxSimilarity", () => {
|
|
66
77
|
const scores = new Float32Array([0.3, 0.9, 0.1, 0.7, 0.5]);
|
|
67
|
-
const results =
|
|
78
|
+
const results = queryResults(scores, resolveKey, { limit: Infinity, order: "descend", maxSimilarity: 0.7 });
|
|
79
|
+
|
|
80
|
+
expect(results).toHaveLength(4);
|
|
81
|
+
expect(results[0].key).toBe("date");
|
|
82
|
+
expect(results[0].similarity).toBeCloseTo(0.7, 4);
|
|
83
|
+
expect(results[1].key).toBe("elderberry");
|
|
84
|
+
expect(results[2].key).toBe("apple");
|
|
85
|
+
expect(results[3].key).toBe("cherry");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("maxSimilarity is inclusive", () => {
|
|
89
|
+
const scores = new Float32Array([0.5]);
|
|
90
|
+
const results = queryResults(scores, resolveKey, { limit: 10, order: "descend", maxSimilarity: 0.5 });
|
|
91
|
+
expect(results).toHaveLength(1);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("filters by both minSimilarity and maxSimilarity", () => {
|
|
95
|
+
const scores = new Float32Array([0.3, 0.9, 0.1, 0.7, 0.5]);
|
|
96
|
+
const results = queryResults(scores, resolveKey, { limit: Infinity, order: "descend", minSimilarity: 0.3, maxSimilarity: 0.7 });
|
|
97
|
+
|
|
98
|
+
expect(results).toHaveLength(3);
|
|
99
|
+
expect(results[0].key).toBe("date");
|
|
100
|
+
expect(results[1].key).toBe("elderberry");
|
|
101
|
+
expect(results[2].key).toBe("apple");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("handles limit Infinity", () => {
|
|
105
|
+
const scores = new Float32Array([0.3, 0.9, 0.1, 0.7, 0.5]);
|
|
106
|
+
const results = queryResults(scores, resolveKey, { limit: Infinity, order: "descend" });
|
|
107
|
+
expect(results).toHaveLength(5);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("supports full similarity range [-1, 1]", () => {
|
|
111
|
+
const scores = new Float32Array([-1.0, -0.5, 0.0, 0.5, 1.0]);
|
|
112
|
+
const results = queryResults(scores, resolveKey, { limit: Infinity, order: "descend" });
|
|
113
|
+
|
|
68
114
|
expect(results).toHaveLength(5);
|
|
115
|
+
expect(results[0].similarity).toBeCloseTo(1.0, 5);
|
|
116
|
+
expect(results[4].similarity).toBeCloseTo(-1.0, 5);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("handles tiny floating point values near boundaries", () => {
|
|
120
|
+
const epsilon = 1e-7;
|
|
121
|
+
const scores = new Float32Array([0.5 - epsilon, 0.5, 0.5 + epsilon]);
|
|
122
|
+
const results = queryResults(scores, resolveKey, { limit: Infinity, order: "descend", minSimilarity: 0.5 });
|
|
123
|
+
|
|
124
|
+
// 0.5 and 0.5 + epsilon should pass, 0.5 - epsilon should not
|
|
125
|
+
expect(results).toHaveLength(2);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("ascending order with limit returns bottomK", () => {
|
|
129
|
+
const scores = new Float32Array([0.3, 0.9, 0.1, 0.7, 0.5]);
|
|
130
|
+
const results = queryResults(scores, resolveKey, { limit: 2, order: "ascend" });
|
|
131
|
+
|
|
132
|
+
expect(results).toHaveLength(2);
|
|
133
|
+
expect(results[0].key).toBe("cherry");
|
|
134
|
+
expect(results[0].similarity).toBeCloseTo(0.1, 4);
|
|
135
|
+
expect(results[1].key).toBe("apple");
|
|
136
|
+
expect(results[1].similarity).toBeCloseTo(0.3, 4);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("ascending order with minSimilarity and maxSimilarity", () => {
|
|
140
|
+
const scores = new Float32Array([-1.0, -0.5, 0.0, 0.5, 1.0]);
|
|
141
|
+
const results = queryResults(scores, resolveKey, { limit: Infinity, order: "ascend", minSimilarity: -0.5, maxSimilarity: 0.5 });
|
|
142
|
+
|
|
143
|
+
expect(results).toHaveLength(3);
|
|
144
|
+
expect(results[0].similarity).toBeCloseTo(-0.5, 5);
|
|
145
|
+
expect(results[1].similarity).toBeCloseTo(0.0, 5);
|
|
146
|
+
expect(results[2].similarity).toBeCloseTo(0.5, 5);
|
|
69
147
|
});
|
|
70
148
|
});
|
|
71
149
|
|
|
@@ -75,7 +153,7 @@ describe("iterableResults", () => {
|
|
|
75
153
|
|
|
76
154
|
it("sorts results by descending similarity", () => {
|
|
77
155
|
const scores = new Float32Array([0.3, 0.9, 0.1, 0.7, 0.5]);
|
|
78
|
-
const results = [...iterableResults(scores, resolveKey,
|
|
156
|
+
const results = [...iterableResults(scores, resolveKey, { limit: Infinity, order: "descend" })];
|
|
79
157
|
|
|
80
158
|
expect(results).toHaveLength(5);
|
|
81
159
|
expect(results[0].key).toBe("banana");
|
|
@@ -84,16 +162,26 @@ describe("iterableResults", () => {
|
|
|
84
162
|
expect(results[4].key).toBe("cherry");
|
|
85
163
|
});
|
|
86
164
|
|
|
87
|
-
it("
|
|
165
|
+
it("sorts results by ascending similarity", () => {
|
|
88
166
|
const scores = new Float32Array([0.3, 0.9, 0.1, 0.7, 0.5]);
|
|
89
|
-
const results = [...iterableResults(scores, resolveKey,
|
|
167
|
+
const results = [...iterableResults(scores, resolveKey, { limit: Infinity, order: "ascend" })];
|
|
168
|
+
|
|
169
|
+
expect(results).toHaveLength(5);
|
|
170
|
+
expect(results[0].key).toBe("cherry");
|
|
171
|
+
expect(results[0].similarity).toBeCloseTo(0.1, 4);
|
|
172
|
+
expect(results[4].key).toBe("banana");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("respects limit", () => {
|
|
176
|
+
const scores = new Float32Array([0.3, 0.9, 0.1, 0.7, 0.5]);
|
|
177
|
+
const results = [...iterableResults(scores, resolveKey, { limit: 3, order: "descend" })];
|
|
90
178
|
|
|
91
179
|
expect(results).toHaveLength(3);
|
|
92
180
|
expect(results[0].key).toBe("banana");
|
|
93
181
|
});
|
|
94
182
|
|
|
95
183
|
it("handles empty scores", () => {
|
|
96
|
-
const results = [...iterableResults(new Float32Array(0), resolveKey, 10)];
|
|
184
|
+
const results = [...iterableResults(new Float32Array(0), resolveKey, { limit: 10, order: "descend" })];
|
|
97
185
|
expect(results).toEqual([]);
|
|
98
186
|
});
|
|
99
187
|
|
|
@@ -105,7 +193,7 @@ describe("iterableResults", () => {
|
|
|
105
193
|
};
|
|
106
194
|
|
|
107
195
|
const scores = new Float32Array([0.3, 0.9, 0.1]);
|
|
108
|
-
const iterable = iterableResults(scores, lazyResolver,
|
|
196
|
+
const iterable = iterableResults(scores, lazyResolver, { limit: Infinity, order: "descend" });
|
|
109
197
|
|
|
110
198
|
expect(callCount).toBe(0); // no key resolved yet
|
|
111
199
|
|
|
@@ -119,7 +207,7 @@ describe("iterableResults", () => {
|
|
|
119
207
|
|
|
120
208
|
it("is re-iterable", () => {
|
|
121
209
|
const scores = new Float32Array([0.3, 0.9, 0.1]);
|
|
122
|
-
const iterable = iterableResults(scores, resolveKey,
|
|
210
|
+
const iterable = iterableResults(scores, resolveKey, { limit: Infinity, order: "descend" });
|
|
123
211
|
|
|
124
212
|
const first = [...iterable];
|
|
125
213
|
const second = [...iterable];
|
|
@@ -128,7 +216,7 @@ describe("iterableResults", () => {
|
|
|
128
216
|
|
|
129
217
|
it("supports partial iteration (early break)", () => {
|
|
130
218
|
const scores = new Float32Array([0.1, 0.2, 0.3, 0.4, 0.5]);
|
|
131
|
-
const iterable = iterableResults(scores, resolveKey,
|
|
219
|
+
const iterable = iterableResults(scores, resolveKey, { limit: Infinity, order: "descend" });
|
|
132
220
|
|
|
133
221
|
const partial: string[] = [];
|
|
134
222
|
for (const item of iterable) {
|
|
@@ -140,10 +228,9 @@ describe("iterableResults", () => {
|
|
|
140
228
|
expect(partial[1]).toBe("date"); // similarity 0.4
|
|
141
229
|
});
|
|
142
230
|
|
|
143
|
-
it("
|
|
231
|
+
it("filters by minSimilarity", () => {
|
|
144
232
|
const scores = new Float32Array([0.3, 0.9, 0.1, 0.7, 0.5]);
|
|
145
|
-
|
|
146
|
-
const results = [...iterableResults(scores, resolveKey, 5, 0.5)];
|
|
233
|
+
const results = [...iterableResults(scores, resolveKey, { limit: Infinity, order: "descend", minSimilarity: 0.5 })];
|
|
147
234
|
|
|
148
235
|
expect(results).toHaveLength(3);
|
|
149
236
|
expect(results[0].key).toBe("banana");
|
|
@@ -153,8 +240,40 @@ describe("iterableResults", () => {
|
|
|
153
240
|
|
|
154
241
|
it("minSimilarity is inclusive", () => {
|
|
155
242
|
const scores = new Float32Array([0.5]);
|
|
156
|
-
|
|
157
|
-
const results = [...iterableResults(scores, resolveKey, 10, 0.5)];
|
|
243
|
+
const results = [...iterableResults(scores, resolveKey, { limit: Infinity, order: "descend", minSimilarity: 0.5 })];
|
|
158
244
|
expect(results).toHaveLength(1);
|
|
159
245
|
});
|
|
246
|
+
|
|
247
|
+
it("filters by maxSimilarity", () => {
|
|
248
|
+
const scores = new Float32Array([0.3, 0.9, 0.1, 0.7, 0.5]);
|
|
249
|
+
const results = [...iterableResults(scores, resolveKey, { limit: Infinity, order: "descend", maxSimilarity: 0.7 })];
|
|
250
|
+
|
|
251
|
+
expect(results).toHaveLength(4);
|
|
252
|
+
expect(results[0].key).toBe("date");
|
|
253
|
+
expect(results[3].key).toBe("cherry");
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("maxSimilarity is inclusive", () => {
|
|
257
|
+
const scores = new Float32Array([0.5]);
|
|
258
|
+
const results = [...iterableResults(scores, resolveKey, { limit: Infinity, order: "descend", maxSimilarity: 0.5 })];
|
|
259
|
+
expect(results).toHaveLength(1);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("ascending order with limit returns bottomK", () => {
|
|
263
|
+
const scores = new Float32Array([0.3, 0.9, 0.1, 0.7, 0.5]);
|
|
264
|
+
const results = [...iterableResults(scores, resolveKey, { limit: 2, order: "ascend" })];
|
|
265
|
+
|
|
266
|
+
expect(results).toHaveLength(2);
|
|
267
|
+
expect(results[0].key).toBe("cherry");
|
|
268
|
+
expect(results[1].key).toBe("apple");
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("supports full similarity range [-1, 1]", () => {
|
|
272
|
+
const scores = new Float32Array([-1.0, -0.5, 0.0, 0.5, 1.0]);
|
|
273
|
+
const results = [...iterableResults(scores, resolveKey, { limit: Infinity, order: "ascend" })];
|
|
274
|
+
|
|
275
|
+
expect(results).toHaveLength(5);
|
|
276
|
+
expect(results[0].similarity).toBeCloseTo(-1.0, 5);
|
|
277
|
+
expect(results[4].similarity).toBeCloseTo(1.0, 5);
|
|
278
|
+
});
|
|
160
279
|
});
|