usearch 2.17.1 → 2.19.4

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.
Files changed (47) hide show
  1. package/README.md +6 -2
  2. package/c/lib.cpp +5 -0
  3. package/c/module.modulemap +4 -0
  4. package/c/usearch.h +12 -4
  5. package/cpp/README.md +1 -1
  6. package/cpp/bench.cpp +1 -1
  7. package/cpp/test.cpp +110 -16
  8. package/include/usearch/index.hpp +48 -35
  9. package/include/usearch/index_dense.hpp +34 -19
  10. package/include/usearch/index_plugins.hpp +29 -7
  11. package/javascript/README.md +8 -6
  12. package/javascript/dist/cjs/usearch.d.ts +9 -4
  13. package/javascript/dist/cjs/usearch.js +23 -8
  14. package/javascript/dist/esm/usearch.d.ts +9 -4
  15. package/javascript/dist/esm/usearch.js +21 -7
  16. package/javascript/lib.cpp +137 -59
  17. package/javascript/usearch.test.js +359 -192
  18. package/javascript/usearch.ts +35 -11
  19. package/package.json +1 -1
  20. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  21. package/prebuilds/linux-arm64/usearch.node +0 -0
  22. package/prebuilds/linux-x64/usearch.node +0 -0
  23. package/simsimd/CONTRIBUTING.md +23 -8
  24. package/simsimd/Cargo.lock +1 -1
  25. package/simsimd/Cargo.toml +1 -1
  26. package/simsimd/README.md +49 -9
  27. package/simsimd/build.rs +7 -3
  28. package/simsimd/c/lib.c +6 -1
  29. package/simsimd/include/simsimd/binary.h +3 -3
  30. package/simsimd/include/simsimd/dot.h +2 -2
  31. package/simsimd/include/simsimd/elementwise.h +26 -17
  32. package/simsimd/include/simsimd/simsimd.h +125 -49
  33. package/simsimd/include/simsimd/sparse.h +30 -29
  34. package/simsimd/include/simsimd/spatial.h +4 -3
  35. package/simsimd/include/simsimd/types.h +19 -8
  36. package/simsimd/javascript/lib.c +0 -1
  37. package/simsimd/package-lock.json +411 -362
  38. package/simsimd/package.json +9 -9
  39. package/simsimd/rust/lib.rs +63 -29
  40. package/simsimd/scripts/bench.cxx +13 -0
  41. package/simsimd/scripts/bench_cosine.rs +8 -0
  42. package/simsimd/scripts/bench_native.rs +4 -0
  43. package/simsimd/scripts/bench_sqeuclidean.rs +8 -0
  44. package/simsimd/scripts/test.c +22 -0
  45. package/simsimd/scripts/test.py +80 -4
  46. package/simsimd/swift/SimSIMD.swift +17 -0
  47. package/simsimd/swift/Test.swift +36 -0
@@ -1,4 +1,35 @@
1
- const test = require('node:test');
1
+ const nodeTest = require('node:test');
2
+ const realTest = nodeTest.test; // the original function
3
+
4
+ function loggedTest(name, options, fn) {
5
+ // The API has two call signatures:
6
+ // test(name, fn)
7
+ // test(name, options, fn)
8
+ if (typeof options === 'function') {
9
+ fn = options;
10
+ options = undefined;
11
+ }
12
+
13
+ // Wrap the body so we can log before / after
14
+ const wrapped = async (t) => {
15
+ console.log('▶', name);
16
+ try {
17
+ await fn(t); // run the user’s test
18
+ console.log('✓', name);
19
+ } catch (err) {
20
+ console.log('✖', name);
21
+ throw err; // re-throw so the runner records the failure
22
+ }
23
+ };
24
+
25
+ // Delegate back to the real test() with the same options
26
+ return options ? realTest(name, options, wrapped) : realTest(name, wrapped);
27
+ }
28
+
29
+ // Replace both the export and the global the runner puts on each module
30
+ global.test = loggedTest;
31
+ module.exports = loggedTest; // for completeness if this file is `require`d
32
+
2
33
  const assert = require('node:assert');
3
34
  const fs = require('node:fs');
4
35
  const os = require('node:os');
@@ -6,250 +37,386 @@ const path = require('node:path');
6
37
  const usearch = require('./dist/cjs/usearch.js');
7
38
 
8
39
  function assertAlmostEqual(actual, expected, tolerance = 1e-6) {
9
- const lowerBound = expected - tolerance;
10
- const upperBound = expected + tolerance;
11
- assert(
12
- actual >= lowerBound && actual <= upperBound,
13
- `Expected ${actual} to be almost equal to ${expected}`
14
- );
40
+ const lowerBound = expected - tolerance;
41
+ const upperBound = expected + tolerance;
42
+ assert(
43
+ actual >= lowerBound && actual <= upperBound,
44
+ `Expected ${actual} to be almost equal to ${expected}`
45
+ );
15
46
  }
16
47
 
48
+ const THREADS = 2;
17
49
 
18
50
  test('Single-entry operations', async (t) => {
19
- await t.test('index info', () => {
20
- const index = new usearch.Index(2, 'l2sq');
51
+ await t.test('index info', () => {
52
+ const index = new usearch.Index(2, 'l2sq');
21
53
 
22
- assert.equal(index.connectivity(), 16, 'connectivity should be 16');
23
- assert.equal(index.dimensions(), 2, 'dimensions should be 2');
24
- assert.equal(index.size(), 0, 'initial size should be 0');
25
- });
54
+ assert.equal(index.connectivity(), 16, 'connectivity should be 16');
55
+ assert.equal(index.dimensions(), 2, 'dimensions should be 2');
56
+ assert.equal(index.size(), 0, 'initial size should be 0');
57
+ });
26
58
 
27
- await t.test('add', () => {
28
- const index = new usearch.Index(2, 'l2sq');
59
+ await t.test('add', () => {
60
+ const index = new usearch.Index(2, 'l2sq');
29
61
 
30
- index.add(15n, new Float32Array([10, 20]));
31
- index.add(16n, new Float32Array([10, 25]));
62
+ index.add(15n, new Float32Array([10, 20]));
63
+ index.add(16n, new Float32Array([10, 25]));
32
64
 
33
- assert.equal(index.size(), 2, 'size after adding elements should be 2');
34
- assert.equal(index.contains(15), true, 'entry must be present after insertion');
65
+ assert.equal(index.size(), 2, 'size after adding elements should be 2');
66
+ assert.equal(
67
+ index.contains(15),
68
+ true,
69
+ 'entry must be present after insertion'
70
+ );
35
71
 
36
- const results = index.search(new Float32Array([13, 14]), 2);
72
+ const results = index.search(new Float32Array([13, 14]), 2);
37
73
 
38
- assert.deepEqual(results.keys, new BigUint64Array([15n, 16n]), 'keys should be 15 and 16');
39
- assert.deepEqual(results.distances, new Float32Array([45, 130]), 'distances should be 45 and 130');
40
- });
74
+ assert.deepEqual(
75
+ results.keys,
76
+ new BigUint64Array([15n, 16n]),
77
+ 'keys should be 15 and 16'
78
+ );
79
+ assert.deepEqual(
80
+ results.distances,
81
+ new Float32Array([45, 130]),
82
+ 'distances should be 45 and 130'
83
+ );
84
+ });
41
85
 
42
- await t.test('remove', () => {
43
- const index = new usearch.Index(2, 'l2sq');
86
+ await t.test('remove', () => {
87
+ const index = new usearch.Index(2, 'l2sq');
44
88
 
45
- index.add(15n, new Float32Array([10, 20]));
46
- index.add(16n, new Float32Array([10, 25]));
47
- index.add(25n, new Float32Array([20, 40]));
48
- index.add(26n, new Float32Array([20, 45]));
89
+ index.add(15n, new Float32Array([10, 20]));
90
+ index.add(16n, new Float32Array([10, 25]));
91
+ index.add(25n, new Float32Array([20, 40]));
92
+ index.add(26n, new Float32Array([20, 45]));
49
93
 
50
- assert.equal(index.remove(15n), 1);
94
+ assert.equal(index.remove(15n), 1);
51
95
 
52
- assert.equal(index.size(), 3, 'size after remoing elements should be 3');
53
- assert.equal(index.contains(15), false, 'entry must be absent after insertion');
96
+ assert.equal(index.size(), 3, 'size after removing elements should be 3');
97
+ assert.equal(
98
+ index.contains(15),
99
+ false,
100
+ 'entry must be absent after insertion'
101
+ );
54
102
 
55
- const results = index.search(new Float32Array([13, 14]), 2);
103
+ const results = index.search(new Float32Array([13, 14]), 2);
56
104
 
57
- assert.deepEqual(results.keys, new BigUint64Array([16n, 25n]), 'keys should not include 15');
58
- });
105
+ assert.deepEqual(
106
+ results.keys,
107
+ new BigUint64Array([16n, 25n]),
108
+ 'keys should not include 15'
109
+ );
110
+ });
59
111
  });
60
112
 
61
113
  test('Batch operations', async (t) => {
62
- await t.test('add', () => {
63
- const indexBatch = new usearch.Index(2, 'l2sq');
114
+ await t.test('add', () => {
115
+ const indexBatch = new usearch.Index(2, 'l2sq');
64
116
 
65
- const keys = [15n, 16n];
66
- const vectors = [new Float32Array([10, 20]), new Float32Array([10, 25])];
117
+ const keys = [15n, 16n];
118
+ const vectors = [new Float32Array([10, 20]), new Float32Array([10, 25])];
67
119
 
68
- indexBatch.add(keys, vectors);
69
- assert.equal(indexBatch.size(), 2, 'size after adding batch should be 2');
120
+ indexBatch.add(keys, vectors);
121
+ assert.equal(indexBatch.size(), 2, 'size after adding batch should be 2');
70
122
 
71
- const results = indexBatch.search(new Float32Array([13, 14]), 2);
123
+ const results = indexBatch.search(new Float32Array([13, 14]), 2);
72
124
 
73
- assert.deepEqual(results.keys, new BigUint64Array([15n, 16n]), 'keys should be 15 and 16');
74
- assert.deepEqual(results.distances, new Float32Array([45, 130]), 'distances should be 45 and 130');
75
- });
125
+ assert.deepEqual(
126
+ results.keys,
127
+ new BigUint64Array([15n, 16n]),
128
+ 'keys should be 15 and 16'
129
+ );
130
+ assert.deepEqual(
131
+ results.distances,
132
+ new Float32Array([45, 130]),
133
+ 'distances should be 45 and 130'
134
+ );
135
+ });
76
136
 
77
- await t.test('remove', () => {
78
- const indexBatch = new usearch.Index(2, 'l2sq');
137
+ await t.test('remove', () => {
138
+ const indexBatch = new usearch.Index(2, 'l2sq');
79
139
 
80
- const keys = [15n, 16n, 25n, 26n];
81
- const vectors = [
82
- new Float32Array([10, 20]),
83
- new Float32Array([10, 25]),
84
- new Float32Array([20, 40]),
85
- new Float32Array([20, 45])
86
- ];
87
- indexBatch.add(keys, vectors);
140
+ const keys = [15n, 16n, 25n, 26n];
141
+ const vectors = [
142
+ new Float32Array([10, 20]),
143
+ new Float32Array([10, 25]),
144
+ new Float32Array([20, 40]),
145
+ new Float32Array([20, 45]),
146
+ ];
147
+ indexBatch.add(keys, vectors);
88
148
 
89
- assert.deepEqual(indexBatch.remove([15n, 25n]), [1, 1])
90
- assert.equal(indexBatch.size(), 2, 'size after removing batch should be 2');
149
+ assert.deepEqual(indexBatch.remove([15n, 25n]), [1, 1]);
150
+ assert.equal(indexBatch.size(), 2, 'size after removing batch should be 2');
91
151
 
92
- const results = indexBatch.search(new Float32Array([13, 14]), 2);
152
+ const results = indexBatch.search(new Float32Array([13, 14]), 2);
153
+
154
+ assert.deepEqual(
155
+ results.keys,
156
+ new BigUint64Array([16n, 26n]),
157
+ 'keys should not include 15 and 25'
158
+ );
159
+ });
160
+
161
+ await t.test('search', () => {
162
+ const indexBatch = new usearch.Index(3, 'l2sq');
163
+
164
+ const keys = [15n, 16n];
165
+ const vectors = [new Float32Array([10, 20, 30]), new Float32Array([10, 25, 35])];
166
+
167
+ indexBatch.add(keys, vectors);
168
+ assert.equal(indexBatch.size(), 2, 'size after adding batch should be 2');
169
+
170
+ const results = indexBatch.search([new Float32Array([13, 14, 15]), new Float32Array([13, 14, 15])], 2);
171
+
172
+ assert.equal(results.k, 2, 'k should be 2');
173
+
174
+ assert.deepEqual(
175
+ results.counts,
176
+ new BigUint64Array([ 2n, 2n ]) ,
177
+ 'counts should be 2 for both keys'
178
+ );
179
+
180
+ assert.deepEqual(
181
+ results.keys,
182
+ new BigUint64Array([15n, 16n, 15n, 16n]),
183
+ 'keys should be 15 and 16'
184
+ );
185
+ assert.deepEqual(
186
+ results.distances,
187
+ new Float32Array( [ 270, 530, 270, 530 ]),
188
+ 'distances should be 270 and 530'
189
+ );
190
+ });
93
191
 
94
- assert.deepEqual(results.keys, new BigUint64Array([16n, 26n]), 'keys should not include 15 and 25');
95
- });
96
192
  });
97
193
 
98
- test("Expected results", () => {
99
- const index = new usearch.Index({
100
- metric: "l2sq",
101
- connectivity: 16,
102
- dimensions: 3,
103
- });
104
- index.add(42n, new Float32Array([0.2, 0.6, 0.4]));
105
- const results = index.search(new Float32Array([0.2, 0.6, 0.4]), 10);
194
+ test('Expected results', () => {
195
+ const index = new usearch.Index({
196
+ metric: 'l2sq',
197
+ connectivity: 16,
198
+ dimensions: 3,
199
+ });
200
+ index.add(42n, new Float32Array([0.2, 0.6, 0.4]));
201
+ const results = index.search(new Float32Array([0.2, 0.6, 0.4]), 10);
202
+
203
+ assert.equal(index.size(), 1);
204
+ assert.deepEqual(results.keys, new BigUint64Array([42n]));
205
+ assertAlmostEqual(results.distances[0], new Float32Array([0]));
206
+ });
106
207
 
107
- assert.equal(index.size(), 1);
108
- assert.deepEqual(results.keys, new BigUint64Array([42n]));
109
- assertAlmostEqual(results.distances[0], new Float32Array([0]));
208
+ test('Multithread search returns same results', () => {
209
+ const index = new usearch.Index({
210
+ metric: 'l2sq',
211
+ connectivity: 16,
212
+ dimensions: 3,
213
+ });
214
+ index.add(42n, new Float32Array([0.2, 0.6, 0.4]));
215
+ assert.equal(index.size(), 1);
216
+
217
+ const results_1 = index.search(new Float32Array([0.2, 0.6, 0.4]), 10, 0);
218
+ const results_2 = index.search(
219
+ new Float32Array([0.2, 0.6, 0.4]),
220
+ 10,
221
+ THREADS
222
+ );
223
+
224
+ assert.deepEqual(results_1.keys, results_2.keys);
225
+ assertAlmostEqual(results_1.distances, results_2.distances);
110
226
  });
111
227
 
112
- test('Expected count()', async (t) => {
113
- const index = new usearch.Index({
114
- metric: 'l2sq',
115
- connectivity: 16,
116
- dimensions: 3,
117
- });
118
- index.add(
119
- [42n, 43n],
120
- [new Float32Array([0.2, 0.6, 0.4]), new Float32Array([0.2, 0.6, 0.4])]
228
+ test('Exact search', async (t) => {
229
+ const dataset = [
230
+ new Float32Array([0.2, 0.6, 0.4]),
231
+ new Float32Array([0.6, 0.6, 0.4]),
232
+ ];
233
+ const queries = new Float32Array([0.2, 0.6, 0.4]);
234
+ const dimensions = 3;
235
+ const count = 2;
236
+ const metric = 'l2sq';
237
+
238
+ await t.test('Single core search', () => {
239
+ const result = usearch.exactSearch(
240
+ dataset,
241
+ queries,
242
+ dimensions,
243
+ count,
244
+ metric,
245
+ 1 // threads
121
246
  );
122
247
 
123
- await t.test('Argument is a number', () => {
124
- assert.equal(1, index.count(43n));
125
- });
126
- await t.test('Argument is a number (does not exist)', () => {
127
- assert.equal(0, index.count(44n));
128
- });
129
- await t.test('Argument is an array', () => {
130
- assert.deepEqual([1, 1, 0], index.count([42n, 43n, 44n]));
131
- });
248
+ assert.deepEqual(result.keys, new BigUint64Array([0n, 1n]));
249
+ assertAlmostEqual(new Float32Array([0]), result.distances[0]);
250
+ assertAlmostEqual(new Float32Array([0.16]), result.distances[1]);
251
+ });
252
+
253
+ await t.test('Multithreaded search', () => {
254
+ const result = usearch.exactSearch(
255
+ dataset,
256
+ queries,
257
+ dimensions,
258
+ count,
259
+ metric,
260
+ THREADS // threads
261
+ );
262
+
263
+ assert.deepEqual(result.keys, new BigUint64Array([0n, 1n]));
264
+ assertAlmostEqual(new Float32Array([0]), result.distances[0]);
265
+ assertAlmostEqual(new Float32Array([0.16]), result.distances[1]);
266
+ });
267
+ });
268
+
269
+ test('Expected count()', async (t) => {
270
+ const index = new usearch.Index({
271
+ metric: 'l2sq',
272
+ connectivity: 16,
273
+ dimensions: 3,
274
+ });
275
+ index.add(
276
+ [42n, 43n],
277
+ [new Float32Array([0.2, 0.6, 0.4]), new Float32Array([0.2, 0.6, 0.4])]
278
+ );
279
+
280
+ await t.test('Argument is a number', () => {
281
+ assert.equal(1, index.count(43n));
282
+ });
283
+ await t.test('Argument is a number (does not exist)', () => {
284
+ assert.equal(0, index.count(44n));
285
+ });
286
+ await t.test('Argument is an array', () => {
287
+ assert.deepEqual([1, 1, 0], index.count([42n, 43n, 44n]));
288
+ });
132
289
  });
133
290
 
134
291
  test('Operations with invalid values', () => {
135
- const indexBatch = new usearch.Index(2, 'l2sq');
292
+ const indexBatch = new usearch.Index(2, 'l2sq');
136
293
 
137
- const keys = [NaN, 16n];
138
- const vectors = [new Float32Array([10, 30]), new Float32Array([1, 5])];
294
+ const keys = [NaN, 16n];
295
+ const vectors = [new Float32Array([10, 30]), new Float32Array([1, 5])];
139
296
 
140
- assert.throws(
141
- () => indexBatch.add(keys, vectors),
142
- {
143
- name: 'Error',
144
- message: 'All keys must be positive integers or bigints.'
145
- }
146
- );
297
+ // All keys must be positive integers or bigints
298
+ assert.throws(() => indexBatch.add(keys, vectors));
147
299
 
148
- assert.throws(
149
- () => indexBatch.search(NaN, 2),
150
- {
151
- name: 'Error',
152
- message: 'Vectors must be a TypedArray or an array of arrays.'
153
- }
154
- );
300
+ // Vectors must be a TypedArray or an array of arrays
301
+ assert.throws(() => indexBatch.search(NaN, 2));
155
302
  });
156
303
 
157
304
  test('Invalid operations', async (t) => {
158
- await t.test('Add the same keys', () => {
159
- const index = new usearch.Index({
160
- metric: "l2sq",
161
- connectivity: 16,
162
- dimensions: 3,
163
- });
164
- index.add(42n, new Float32Array([0.2, 0.6, 0.4]));
165
- assert.throws(
166
- () => index.add(42n, new Float32Array([0.2, 0.6, 0.4])),
167
- {
168
- name: 'Error',
169
- message: 'Duplicate keys not allowed in high-level wrappers'
170
- }
171
- );
305
+ await t.test('Add the same keys', () => {
306
+ const index = new usearch.Index({
307
+ metric: 'l2sq',
308
+ connectivity: 16,
309
+ dimensions: 3,
172
310
  });
173
- });
311
+ index.add(42n, new Float32Array([0.2, 0.6, 0.4]));
312
+ assert.throws(() => index.add(42n, new Float32Array([0.2, 0.6, 0.4])));
313
+ });
174
314
 
315
+ await t.test('Batch add containing the same key', () => {
316
+ const index = new usearch.Index({
317
+ metric: 'l2sq',
318
+ connectivity: 16,
319
+ dimensions: 3,
320
+ });
321
+ index.add(42n, new Float32Array([0.2, 0.6, 0.4]));
322
+ assert.throws(() => {
323
+ index.add(
324
+ [41n, 42n, 43n],
325
+ [
326
+ [0.1, 0.6, 0.4],
327
+ [0.2, 0.6, 0.4],
328
+ [0.3, 0.6, 0.4],
329
+ ]
330
+ );
331
+ });
332
+ });
333
+ });
175
334
 
176
335
  test('Serialization', async (t) => {
177
- const indexPath = path.join(os.tmpdir(), 'usearch.test.index')
178
-
179
- t.beforeEach(() => {
180
- const index = new usearch.Index({
181
- metric: "l2sq",
182
- connectivity: 16,
183
- dimensions: 3,
184
- });
185
- index.add(42n, new Float32Array([0.2, 0.6, 0.4]));
186
- index.save(indexPath);
187
- });
336
+ const indexPath = path.join(os.tmpdir(), 'usearch.test.index');
188
337
 
189
- t.afterEach(() => {
190
- fs.unlinkSync(indexPath);
338
+ t.beforeEach(() => {
339
+ const index = new usearch.Index({
340
+ metric: 'l2sq',
341
+ connectivity: 16,
342
+ dimensions: 3,
191
343
  });
344
+ index.add(42n, new Float32Array([0.2, 0.6, 0.4]));
345
+ index.save(indexPath);
346
+ });
192
347
 
193
- await t.test('load', () => {
194
- const index = new usearch.Index({
195
- metric: "l2sq",
196
- connectivity: 16,
197
- dimensions: 3,
198
- });
199
- index.load(indexPath);
200
- const results = index.search(new Float32Array([0.2, 0.6, 0.4]), 10);
201
-
202
- assert.equal(index.size(), 1);
203
- assert.deepEqual(results.keys, new BigUint64Array([42n]));
204
- assertAlmostEqual(results.distances[0], new Float32Array([0]));
205
- });
348
+ t.afterEach(() => {
349
+ fs.unlinkSync(indexPath);
350
+ });
206
351
 
207
- // todo: Skip as the test fails only on windows.
208
- // The following error in afterEach().
209
- // `error: "EBUSY: resource busy or locked, unlink`
210
- await t.test('view: Read data', {skip: process.platform === 'win32'}, () => {
211
- const index = new usearch.Index({
212
- metric: "l2sq",
213
- connectivity: 16,
214
- dimensions: 3,
215
- });
216
- index.view(indexPath);
217
- const results = index.search(new Float32Array([0.2, 0.6, 0.4]), 10);
218
-
219
- assert.equal(index.size(), 1);
220
- assert.deepEqual(results.keys, new BigUint64Array([42n]));
221
- assertAlmostEqual(results.distances[0], new Float32Array([0]));
352
+ await t.test('load', () => {
353
+ const index = new usearch.Index({
354
+ metric: 'l2sq',
355
+ connectivity: 16,
356
+ dimensions: 3,
222
357
  });
358
+ index.load(indexPath);
359
+ const results = index.search(
360
+ new Float32Array([0.2, 0.6, 0.4]),
361
+ 10,
362
+ THREADS
363
+ );
223
364
 
224
- await t.test('view: Invalid operations: add', {skip: process.platform === 'win32'}, () => {
225
- const index = new usearch.Index({
226
- metric: "l2sq",
227
- connectivity: 16,
228
- dimensions: 3,
229
- });
230
- index.view(indexPath);
231
- assert.throws(
232
- () => index.add(43n, new Float32Array([0.2, 0.6, 0.4])),
233
- {
234
- name: 'Error',
235
- message: "Can't add to an immutable index"
236
- }
237
- );
238
- });
365
+ assert.equal(index.size(), 1);
366
+ assert.deepEqual(results.keys, new BigUint64Array([42n]));
367
+ assertAlmostEqual(results.distances[0], new Float32Array([0]));
368
+ });
369
+
370
+ // todo: Skip as the test fails only on windows.
371
+ // The following error in afterEach().
372
+ // `error: "EBUSY: resource busy or locked, unlink`
373
+ await t.test(
374
+ 'view: Read data',
375
+ { skip: process.platform === 'win32' },
376
+ () => {
377
+ const index = new usearch.Index({
378
+ metric: 'l2sq',
379
+ connectivity: 16,
380
+ dimensions: 3,
381
+ });
382
+ index.view(indexPath);
383
+ const results = index.search(new Float32Array([0.2, 0.6, 0.4]), 10);
384
+
385
+ assert.equal(index.size(), 1);
386
+ assert.deepEqual(results.keys, new BigUint64Array([42n]));
387
+ assertAlmostEqual(results.distances[0], new Float32Array([0]));
388
+ }
389
+ );
390
+
391
+ await t.test(
392
+ 'view: Invalid operations: add',
393
+ { skip: process.platform === 'win32' },
394
+ () => {
395
+ const index = new usearch.Index({
396
+ metric: 'l2sq',
397
+ connectivity: 16,
398
+ dimensions: 3,
399
+ });
400
+ index.view(indexPath);
401
+
402
+ // Can't add to an immutable index
403
+ assert.throws(() => index.add(43n, new Float32Array([0.2, 0.6, 0.4])));
404
+ }
405
+ );
406
+
407
+ await t.test(
408
+ 'view: Invalid operations: remove',
409
+ { skip: process.platform === 'win32' },
410
+ () => {
411
+ const index = new usearch.Index({
412
+ metric: 'l2sq',
413
+ connectivity: 16,
414
+ dimensions: 3,
415
+ });
416
+ index.view(indexPath);
239
417
 
240
- await t.test('view: Invalid operations: remove', {skip: process.platform === 'win32'}, () => {
241
- const index = new usearch.Index({
242
- metric: "l2sq",
243
- connectivity: 16,
244
- dimensions: 3,
245
- });
246
- index.view(indexPath);
247
- assert.throws(
248
- () => index.remove(42n),
249
- {
250
- name: 'Error',
251
- message: "Can't remove from an immutable index"
252
- }
253
- );
254
- });
418
+ // Can't remove from an immutable index
419
+ assert.throws(() => index.remove(42n));
420
+ }
421
+ );
255
422
  });