usearch 2.6.1 → 2.7.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.
Files changed (54) hide show
  1. package/. npmignore +14 -0
  2. package/.cmake-format.py +19 -0
  3. package/.github/workflows/Dockerfile.libs +2 -2
  4. package/.github/workflows/prerelease.yml +85 -64
  5. package/.github/workflows/release.yml +17 -15
  6. package/.gitignore +1 -1
  7. package/.vscode/launch.json +47 -14
  8. package/.vscode/settings.json +7 -0
  9. package/.vscode/tasks.json +44 -8
  10. package/CITATION.cff +1 -1
  11. package/CMakeLists.txt +218 -67
  12. package/Cargo.toml +1 -1
  13. package/README.md +1 -1
  14. package/binding.gyp +3 -2
  15. package/build.gradle +19 -10
  16. package/c/CMakeLists.txt +21 -48
  17. package/c/README.md +5 -5
  18. package/c/lib.cpp +52 -7
  19. package/c/test.c +137 -94
  20. package/c/usearch.h +42 -0
  21. package/cmake/clipp.cmake +0 -1
  22. package/cmake/jemalloc.cmake +21 -37
  23. package/conanfile.py +1 -1
  24. package/cpp/CMakeLists.txt +14 -123
  25. package/cpp/bench.cpp +0 -12
  26. package/cpp/test.cpp +29 -1
  27. package/csharp/nuget/nuget-package.props +1 -1
  28. package/docs/compilation.md +23 -16
  29. package/docs/conf.py +1 -1
  30. package/golang/README.md +52 -51
  31. package/golang/lib.go +1 -1
  32. package/golang/lib_test.go +4 -1
  33. package/include/usearch/index.hpp +3 -3
  34. package/include/usearch/index_plugins.hpp +345 -160
  35. package/javascript/lib.cpp +204 -250
  36. package/javascript/usearch.js +440 -2
  37. package/javascript/usearch.test.js +37 -0
  38. package/package.json +10 -5
  39. package/python/lib.cpp +95 -224
  40. package/python/scripts/bench_exact.py +2 -2
  41. package/python/scripts/test_jit.py +2 -13
  42. package/python/scripts/test_sparse.py +1 -1
  43. package/python/scripts/test_tooling.py +15 -5
  44. package/rust/lib.cpp +7 -4
  45. package/rust/lib.hpp +2 -0
  46. package/rust/lib.rs +128 -8
  47. package/setup.py +14 -4
  48. package/wasm/CMakeLists.txt +4 -12
  49. package/wasm/lib.cpp +1 -1
  50. package/wasmer.toml +1 -1
  51. package/javascript/docs.js +0 -111
  52. package/javascript/test.js +0 -32
  53. package/javascript/usearch.d.ts +0 -110
  54. package/tsconfig.json +0 -8
@@ -20,10 +20,10 @@
20
20
  using namespace unum::usearch;
21
21
  using namespace unum;
22
22
 
23
- class Index : public Napi::ObjectWrap<Index> {
23
+ class CompiledIndex : public Napi::ObjectWrap<CompiledIndex> {
24
24
  public:
25
25
  static Napi::Object Init(Napi::Env env, Napi::Object exports);
26
- Index(Napi::CallbackInfo const& ctx);
26
+ CompiledIndex(Napi::CallbackInfo const& ctx);
27
27
 
28
28
  private:
29
29
  Napi::Value GetDimensions(Napi::CallbackInfo const& ctx);
@@ -39,340 +39,294 @@ class Index : public Napi::ObjectWrap<Index> {
39
39
  Napi::Value Search(Napi::CallbackInfo const& ctx);
40
40
  Napi::Value Remove(Napi::CallbackInfo const& ctx);
41
41
  Napi::Value Contains(Napi::CallbackInfo const& ctx);
42
+ Napi::Value Count(Napi::CallbackInfo const& ctx);
42
43
 
43
44
  std::unique_ptr<index_dense_t> native_;
44
45
  };
45
46
 
46
- Napi::Object Index::Init(Napi::Env env, Napi::Object exports) {
47
+ Napi::Object CompiledIndex::Init(Napi::Env env, Napi::Object exports) {
47
48
  Napi::Function func = DefineClass( //
48
- env, "Index",
49
+ env, "CompiledIndex",
49
50
  {
50
- InstanceMethod("dimensions", &Index::GetDimensions),
51
- InstanceMethod("size", &Index::GetSize),
52
- InstanceMethod("capacity", &Index::GetCapacity),
53
- InstanceMethod("connectivity", &Index::GetConnectivity),
54
- InstanceMethod("add", &Index::Add),
55
- InstanceMethod("search", &Index::Search),
56
- InstanceMethod("remove", &Index::Remove),
57
- InstanceMethod("contains", &Index::Contains),
58
- InstanceMethod("save", &Index::Save),
59
- InstanceMethod("load", &Index::Load),
60
- InstanceMethod("view", &Index::View),
51
+ InstanceMethod("dimensions", &CompiledIndex::GetDimensions),
52
+ InstanceMethod("size", &CompiledIndex::GetSize),
53
+ InstanceMethod("capacity", &CompiledIndex::GetCapacity),
54
+ InstanceMethod("connectivity", &CompiledIndex::GetConnectivity),
55
+ InstanceMethod("add", &CompiledIndex::Add),
56
+ InstanceMethod("search", &CompiledIndex::Search),
57
+ InstanceMethod("remove", &CompiledIndex::Remove),
58
+ InstanceMethod("contains", &CompiledIndex::Contains),
59
+ InstanceMethod("count", &CompiledIndex::Count),
60
+ InstanceMethod("save", &CompiledIndex::Save),
61
+ InstanceMethod("load", &CompiledIndex::Load),
62
+ InstanceMethod("view", &CompiledIndex::View),
61
63
  });
62
64
 
63
65
  Napi::FunctionReference* constructor = new Napi::FunctionReference();
64
66
  *constructor = Napi::Persistent(func);
65
67
  env.SetInstanceData(constructor);
66
68
 
67
- exports.Set("Index", func);
69
+ exports.Set("CompiledIndex", func);
68
70
  return exports;
69
71
  }
70
72
 
71
- Index::Index(Napi::CallbackInfo const& ctx) : Napi::ObjectWrap<Index>(ctx) {
72
- Napi::Env env = ctx.Env();
73
-
74
- int length = ctx.Length();
75
- if (length == 0 || length >= 2 || !ctx[0].IsObject()) {
76
- Napi::TypeError::New(env, "Pass args as named objects: dimensions: uint, capacity: uint, metric: str")
77
- .ThrowAsJavaScriptException();
78
- return;
79
- }
80
-
81
- bool lossless = true;
82
- Napi::Object params = ctx[0].As<Napi::Object>();
83
- std::size_t dimensions =
84
- params.Has("dimensions") ? params.Get("dimensions").As<Napi::BigInt>().Uint64Value(&lossless) : 0;
85
-
86
- index_limits_t limits;
87
- std::size_t connectivity = default_connectivity();
88
- std::size_t expansion_add = default_expansion_add();
89
- std::size_t expansion_search = default_expansion_search();
90
-
91
- if (params.Has("capacity"))
92
- limits.members = params.Get("capacity").As<Napi::BigInt>().Uint64Value(&lossless);
93
- if (params.Has("connectivity"))
94
- connectivity = params.Get("connectivity").As<Napi::BigInt>().Uint64Value(&lossless);
95
- if (params.Has("expansion_add"))
96
- expansion_add = params.Get("expansion_add").As<Napi::BigInt>().Uint64Value(&lossless);
97
- if (params.Has("expansion_search"))
98
- expansion_search = params.Get("expansion_search").As<Napi::BigInt>().Uint64Value(&lossless);
99
- if (!lossless) {
100
- Napi::TypeError::New(env, "Arguments must be unsigned integers").ThrowAsJavaScriptException();
101
- return;
102
- }
73
+ std::size_t napi_argument_to_size(Napi::Value v) {
74
+ return static_cast<std::size_t>(v.As<Napi::Number>().DoubleValue());
75
+ }
103
76
 
104
- scalar_kind_t quantization = scalar_kind_t::f32_k;
105
- if (params.Has("quantization")) {
106
- std::string quantization_str = params.Get("quantization").As<Napi::String>().Utf8Value();
107
- expected_gt<scalar_kind_t> expected = scalar_kind_from_name(quantization_str.c_str(), quantization_str.size());
108
- if (!expected) {
109
- Napi::TypeError::New(env, expected.error.release()).ThrowAsJavaScriptException();
110
- return;
111
- }
112
- quantization = *expected;
113
- }
77
+ CompiledIndex::CompiledIndex(Napi::CallbackInfo const& ctx) : Napi::ObjectWrap<CompiledIndex>(ctx) {
114
78
 
115
- // By default we use the Inner Product similarity
116
- metric_kind_t metric_kind = metric_kind_t::ip_k;
117
- if (params.Has("metric")) {
118
- std::string metric_str = params.Get("metric").As<Napi::String>().Utf8Value();
119
- expected_gt<metric_kind_t> expected = metric_from_name(metric_str.c_str(), metric_str.size());
120
- if (!expected) {
121
- Napi::TypeError::New(env, expected.error.release()).ThrowAsJavaScriptException();
122
- return;
123
- }
124
- metric_kind = *expected;
125
- }
79
+ // Directly assign the parameters without checks
80
+ std::size_t dimensions = napi_argument_to_size(ctx[0]);
81
+ metric_kind_t metric_kind = metric_from_name(ctx[1].As<Napi::String>().Utf8Value().c_str());
82
+ scalar_kind_t quantization = scalar_kind_from_name(ctx[2].As<Napi::String>().Utf8Value().c_str());
83
+ std::size_t connectivity = napi_argument_to_size(ctx[3]);
84
+ std::size_t expansion_add = napi_argument_to_size(ctx[4]);
85
+ std::size_t expansion_search = napi_argument_to_size(ctx[5]);
86
+ bool multi = ctx[6].As<Napi::Boolean>().Value();
126
87
 
127
88
  metric_punned_t metric(dimensions, metric_kind, quantization);
128
89
  index_dense_config_t config(connectivity, expansion_add, expansion_search);
90
+ config.multi = multi;
91
+
129
92
  native_.reset(new index_dense_t(index_dense_t::make(metric, config)));
130
- native_->reserve(limits);
93
+ if (!native_)
94
+ Napi::Error::New(ctx.Env(), "Out of memory!").ThrowAsJavaScriptException();
131
95
  }
132
96
 
133
- Napi::Value Index::GetDimensions(Napi::CallbackInfo const& ctx) {
134
- return Napi::BigInt::New(ctx.Env(), static_cast<std::uint64_t>(native_->dimensions()));
97
+ Napi::Value CompiledIndex::GetDimensions(Napi::CallbackInfo const& ctx) {
98
+ return Napi::Number::New(ctx.Env(), static_cast<std::uint64_t>(native_->dimensions()));
135
99
  }
136
- Napi::Value Index::GetSize(Napi::CallbackInfo const& ctx) {
137
- return Napi::BigInt::New(ctx.Env(), static_cast<std::uint64_t>(native_->size()));
100
+ Napi::Value CompiledIndex::GetConnectivity(Napi::CallbackInfo const& ctx) {
101
+ return Napi::Number::New(ctx.Env(), static_cast<std::uint64_t>(native_->connectivity()));
138
102
  }
139
- Napi::Value Index::GetConnectivity(Napi::CallbackInfo const& ctx) {
140
- return Napi::BigInt::New(ctx.Env(), static_cast<std::uint64_t>(native_->connectivity()));
103
+ Napi::Value CompiledIndex::GetSize(Napi::CallbackInfo const& ctx) {
104
+ return Napi::Number::New(ctx.Env(), static_cast<std::uint64_t>(native_->size()));
141
105
  }
142
- Napi::Value Index::GetCapacity(Napi::CallbackInfo const& ctx) {
143
- return Napi::BigInt::New(ctx.Env(), static_cast<std::uint64_t>(native_->capacity()));
106
+ Napi::Value CompiledIndex::GetCapacity(Napi::CallbackInfo const& ctx) {
107
+ return Napi::Number::New(ctx.Env(), static_cast<std::uint64_t>(native_->capacity()));
144
108
  }
145
109
 
146
- void Index::Save(Napi::CallbackInfo const& ctx) {
147
- Napi::Env env = ctx.Env();
148
-
149
- int length = ctx.Length();
150
- if (length == 0 || !ctx[0].IsString()) {
151
- Napi::TypeError::New(env, "Function expects a string path argument").ThrowAsJavaScriptException();
152
- return;
153
- }
154
-
110
+ void CompiledIndex::Save(Napi::CallbackInfo const& ctx) {
155
111
  try {
156
112
  std::string path = ctx[0].As<Napi::String>();
157
113
  auto result = native_->save(path.c_str());
158
114
  if (!result)
159
- return Napi::TypeError::New(env, result.error.release()).ThrowAsJavaScriptException();
115
+ Napi::TypeError::New(ctx.Env(), result.error.release()).ThrowAsJavaScriptException();
160
116
 
161
117
  } catch (...) {
162
- Napi::TypeError::New(env, "Serialization failed").ThrowAsJavaScriptException();
118
+ Napi::TypeError::New(ctx.Env(), "Serialization failed").ThrowAsJavaScriptException();
163
119
  }
164
120
  }
165
121
 
166
- void Index::Load(Napi::CallbackInfo const& ctx) {
167
- Napi::Env env = ctx.Env();
168
-
169
- int length = ctx.Length();
170
- if (length == 0 || !ctx[0].IsString()) {
171
- Napi::TypeError::New(env, "Function expects a string path argument").ThrowAsJavaScriptException();
172
- return;
173
- }
174
-
122
+ void CompiledIndex::Load(Napi::CallbackInfo const& ctx) {
175
123
  try {
176
124
  std::string path = ctx[0].As<Napi::String>();
177
125
  auto result = native_->load(path.c_str());
178
126
  if (!result)
179
- return Napi::TypeError::New(env, result.error.release()).ThrowAsJavaScriptException();
127
+ Napi::TypeError::New(ctx.Env(), result.error.release()).ThrowAsJavaScriptException();
180
128
 
181
129
  } catch (...) {
182
- Napi::TypeError::New(env, "Loading failed").ThrowAsJavaScriptException();
130
+ Napi::TypeError::New(ctx.Env(), "Loading failed").ThrowAsJavaScriptException();
183
131
  }
184
132
  }
185
133
 
186
- void Index::View(Napi::CallbackInfo const& ctx) {
187
- Napi::Env env = ctx.Env();
188
-
189
- int length = ctx.Length();
190
- if (length == 0 || !ctx[0].IsString()) {
191
- Napi::TypeError::New(env, "Function expects a string path argument").ThrowAsJavaScriptException();
192
- return;
193
- }
134
+ void CompiledIndex::View(Napi::CallbackInfo const& ctx) {
194
135
 
195
136
  try {
196
137
  std::string path = ctx[0].As<Napi::String>();
197
138
  auto result = native_->view(path.c_str());
198
139
  if (!result)
199
- return Napi::TypeError::New(env, result.error.release()).ThrowAsJavaScriptException();
140
+ Napi::TypeError::New(ctx.Env(), result.error.release()).ThrowAsJavaScriptException();
200
141
 
201
142
  } catch (...) {
202
- Napi::TypeError::New(env, "Memory-mapping failed").ThrowAsJavaScriptException();
143
+ Napi::TypeError::New(ctx.Env(), "Memory-mapping failed").ThrowAsJavaScriptException();
203
144
  }
204
145
  }
205
146
 
206
- void Index::Add(Napi::CallbackInfo const& ctx) {
207
- Napi::Env env = ctx.Env();
208
-
209
- if (ctx.Length() < 2)
210
- return Napi::TypeError::New(env, "Expects at least two arguments").ThrowAsJavaScriptException();
211
-
212
- using key_t = typename index_dense_t::key_t;
213
- std::size_t index_dimensions = native_->dimensions();
214
-
215
- auto add = [&](Napi::BigInt key_js, Napi::Float32Array vector_js) {
216
- bool lossless = true;
217
- key_t key = static_cast<key_t>(key_js.Uint64Value(&lossless));
218
- if (!lossless)
219
- return Napi::TypeError::New(env, "Keys must be unsigned integers").ThrowAsJavaScriptException();
147
+ void CompiledIndex::Add(Napi::CallbackInfo const& ctx) {
148
+ // Extract keys and vectors from arguments
149
+ Napi::BigUint64Array keys = ctx[0].As<Napi::BigUint64Array>();
150
+ std::size_t tasks = keys.ElementLength();
220
151
 
221
- float const* vector = vector_js.Data();
222
- std::size_t dimensions = static_cast<std::size_t>(vector_js.ElementLength());
152
+ // Ensure there is enough capacity
153
+ if (native_->size() + tasks >= native_->capacity())
154
+ native_->reserve(ceil2(native_->size() + tasks));
223
155
 
224
- if (dimensions != index_dimensions)
225
- return Napi::TypeError::New(env, "Wrong number of dimensions").ThrowAsJavaScriptException();
226
-
227
- try {
228
- auto result = native_->add(key, vector);
229
- if (!result)
230
- return Napi::TypeError::New(env, result.error.release()).ThrowAsJavaScriptException();
231
-
232
- } catch (std::bad_alloc const&) {
233
- return Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
234
- } catch (...) {
235
- return Napi::TypeError::New(env, "Insertion failed").ThrowAsJavaScriptException();
236
- }
156
+ // Create an instance of the executor with the default number of threads
157
+ auto run_parallel = [&](auto vectors) {
158
+ executor_stl_t executor;
159
+ executor.fixed(tasks, [&](std::size_t /*thread_idx*/, std::size_t task_idx) {
160
+ native_->add(static_cast<default_key_t>(keys[task_idx]), vectors + task_idx * native_->dimensions());
161
+ });
237
162
  };
238
163
 
239
- if (ctx[0].IsArray() && ctx[1].IsArray()) {
240
- Napi::Array keys_js = ctx[0].As<Napi::Array>();
241
- Napi::Array vectors_js = ctx[1].As<Napi::Array>();
242
- auto length = keys_js.Length();
243
-
244
- if (length != vectors_js.Length())
245
- return Napi::TypeError::New(env, "The number of keys must match the number of vectors")
246
- .ThrowAsJavaScriptException();
247
-
248
- if (native_->size() + length >= native_->capacity())
249
- if (!native_->reserve(ceil2(native_->size() + length)))
250
- return Napi::TypeError::New(env, "Out of memory!").ThrowAsJavaScriptException();
251
-
252
- for (std::size_t i = 0; i < length; i++) {
253
- Napi::Value key_js = keys_js[i];
254
- Napi::Value vector_js = vectors_js[i];
255
- add(key_js.As<Napi::BigInt>(), vector_js.As<Napi::Float32Array>());
256
- }
257
-
258
- } else if (ctx[0].IsBigInt() && ctx[1].IsTypedArray()) {
259
- if (native_->size() + 1 >= native_->capacity())
260
- native_->reserve(ceil2(native_->size() + 1));
261
- add(ctx[0].As<Napi::BigInt>(), ctx[1].As<Napi::Float32Array>());
262
- } else
263
- return Napi::TypeError::New(env, "Invalid argument type, expects integral key(s) and float vector(s)")
164
+ Napi::TypedArray vectors = ctx[1].As<Napi::TypedArray>();
165
+ if (vectors.TypedArrayType() == napi_float32_array) {
166
+ run_parallel(vectors.As<Napi::Float32Array>().Data());
167
+ } else if (vectors.TypedArrayType() == napi_float64_array) {
168
+ run_parallel(vectors.As<Napi::Float64Array>().Data());
169
+ } else if (vectors.TypedArrayType() == napi_int8_array) {
170
+ run_parallel(vectors.As<Napi::Int8Array>().Data());
171
+ } else {
172
+ Napi::TypeError::New(ctx.Env(),
173
+ "Unsupported TypedArray. Supported types are Float32Array, Float64Array, and Int8Array.")
264
174
  .ThrowAsJavaScriptException();
175
+ }
265
176
  }
266
177
 
267
- Napi::Value Index::Search(Napi::CallbackInfo const& ctx) {
178
+ Napi::Value CompiledIndex::Search(Napi::CallbackInfo const& ctx) {
268
179
  Napi::Env env = ctx.Env();
269
- if (ctx.Length() < 2 || !ctx[0].IsTypedArray() || !ctx[1].IsBigInt()) {
270
- Napi::TypeError::New(env, "Expects a and the number of wanted results").ThrowAsJavaScriptException();
271
- return {};
272
- }
180
+ Napi::TypedArray queries = ctx[0].As<Napi::TypedArray>();
181
+ std::size_t tasks = queries.ElementLength() / native_->dimensions();
182
+ std::size_t wanted = napi_argument_to_size(ctx[1]);
273
183
 
274
- Napi::Float32Array vector_js = ctx[0].As<Napi::Float32Array>();
275
- Napi::BigInt wanted_js = ctx[1].As<Napi::BigInt>();
276
-
277
- float const* vector = vector_js.Data();
278
- std::size_t dimensions = static_cast<std::size_t>(vector_js.ElementLength());
279
- if (dimensions != native_->dimensions()) {
280
- Napi::TypeError::New(env, "Wrong number of dimensions").ThrowAsJavaScriptException();
281
- return {};
282
- }
283
-
284
- bool lossless = true;
285
- std::uint64_t wanted = wanted_js.Uint64Value(&lossless);
286
- if (!lossless) {
287
- Napi::TypeError::New(env, "Wanted number of matches must be an unsigned integer").ThrowAsJavaScriptException();
288
- return {};
289
- }
184
+ auto run_parallel = [&](auto vectors) {
185
+ Napi::Array result_js = Napi::Array::New(env, 3);
186
+ Napi::BigUint64Array matches_js = Napi::BigUint64Array::New(env, tasks * wanted);
187
+ Napi::Float32Array distances_js = Napi::Float32Array::New(env, tasks * wanted);
188
+ Napi::BigUint64Array counts_js = Napi::BigUint64Array::New(env, tasks);
290
189
 
291
- using key_t = typename index_dense_t::key_t;
292
- Napi::TypedArrayOf<key_t> matches_js = Napi::TypedArrayOf<key_t>::New(env, wanted);
293
- static_assert(std::is_same<std::uint64_t, key_t>::value, "Matches.key interface expects BigUint64Array");
294
- Napi::Float32Array distances_js = Napi::Float32Array::New(env, wanted);
295
- try {
190
+ auto matches_data = matches_js.Data();
191
+ auto distances_data = distances_js.Data();
192
+ auto counts_data = counts_js.Data();
296
193
 
297
- auto result = native_->search(vector, wanted);
298
- if (!result) {
299
- Napi::TypeError::New(env, result.error.release()).ThrowAsJavaScriptException();
300
- return {};
194
+ try {
195
+ executor_stl_t executor;
196
+ executor.fixed(tasks, [&](std::size_t /*thread_idx*/, std::size_t task_idx) {
197
+ auto result = native_->search(vectors + task_idx * native_->dimensions(), wanted);
198
+ if (!result) {
199
+ // Handle the error appropriately
200
+ // For example, log the error or set some flag in the result_js object
201
+ } else {
202
+ counts_data[task_idx] = result.dump_to(matches_data + task_idx * native_->dimensions(),
203
+ distances_data + task_idx * native_->dimensions());
204
+ }
205
+ });
206
+ } catch (std::bad_alloc const&) {
207
+ Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
208
+ } catch (...) {
209
+ Napi::TypeError::New(env, "Search failed").ThrowAsJavaScriptException();
301
210
  }
302
211
 
303
- std::uint64_t count = result.dump_to(matches_js.Data(), distances_js.Data());
304
- Napi::Object result_js = Napi::Object::New(env);
305
- result_js.Set("keys", matches_js);
306
- result_js.Set("distances", distances_js);
307
- result_js.Set("count", Napi::BigInt::New(env, count));
212
+ result_js.Set(0u, matches_js);
213
+ result_js.Set(1u, distances_js);
214
+ result_js.Set(2u, counts_js);
308
215
  return result_js;
309
- } catch (std::bad_alloc const&) {
310
- Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
311
- return {};
312
- } catch (...) {
313
- Napi::TypeError::New(env, "Search failed").ThrowAsJavaScriptException();
314
- return {};
216
+ };
217
+
218
+ if (queries.TypedArrayType() == napi_float32_array) {
219
+ return run_parallel(queries.As<Napi::Float32Array>().Data());
220
+ } else if (queries.TypedArrayType() == napi_float64_array) {
221
+ return run_parallel(queries.As<Napi::Float64Array>().Data());
222
+ } else if (queries.TypedArrayType() == napi_int8_array) {
223
+ return run_parallel(queries.As<Napi::Int8Array>().Data());
224
+ } else {
225
+ Napi::TypeError::New(env,
226
+ "Unsupported TypedArray. Supported types are Float32Array, Float64Array, and Int8Array.")
227
+ .ThrowAsJavaScriptException();
228
+ return env.Null();
315
229
  }
316
230
  }
317
231
 
318
- Napi::Value Index::Remove(Napi::CallbackInfo const& ctx) {
232
+ Napi::Value CompiledIndex::Remove(Napi::CallbackInfo const& ctx) {
319
233
  Napi::Env env = ctx.Env();
320
- if (ctx.Length() < 1 || !ctx[0].IsBigInt()) {
321
- Napi::TypeError::New(env, "Expects an entry identifier").ThrowAsJavaScriptException();
322
- return {};
234
+ Napi::BigUint64Array keys = ctx[0].As<Napi::BigUint64Array>();
235
+ std::size_t length = keys.ElementLength();
236
+ Napi::Array result = Napi::Array::New(env, length);
237
+ for (std::size_t i = 0; i < length; ++i) {
238
+ result[i] = Napi::Number::New(env, native_->remove(static_cast<default_key_t>(keys[i])).completed);
323
239
  }
240
+ return result;
241
+ }
324
242
 
325
- Napi::BigInt key_js = ctx[0].As<Napi::BigInt>();
326
- bool lossless = true;
327
- std::uint64_t key = key_js.Uint64Value(&lossless);
328
- if (!lossless) {
329
- Napi::TypeError::New(env, "Identifier must be an unsigned integer").ThrowAsJavaScriptException();
330
- return {};
331
- }
243
+ Napi::Value CompiledIndex::Contains(Napi::CallbackInfo const& ctx) {
244
+ Napi::Env env = ctx.Env();
245
+ Napi::BigUint64Array keys = ctx[0].As<Napi::BigUint64Array>();
246
+ std::size_t length = keys.ElementLength();
247
+ Napi::Array result = Napi::Array::New(env, length);
248
+ for (std::size_t i = 0; i < length; ++i)
249
+ result[i] = Napi::Boolean::New(env, native_->contains(static_cast<default_key_t>(keys[i])));
250
+ return result;
251
+ }
332
252
 
333
- try {
334
- auto result = native_->remove(key);
335
- if (!result) {
336
- Napi::TypeError::New(env, result.error.release()).ThrowAsJavaScriptException();
337
- return {};
338
- }
339
- return Napi::Boolean::New(env, result.completed);
340
- } catch (std::bad_alloc const&) {
341
- Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
342
- return {};
343
- } catch (...) {
344
- Napi::TypeError::New(env, "Search failed").ThrowAsJavaScriptException();
345
- return {};
346
- }
253
+ Napi::Value CompiledIndex::Count(Napi::CallbackInfo const& ctx) {
254
+ Napi::Env env = ctx.Env();
255
+ Napi::BigUint64Array keys = ctx[0].As<Napi::BigUint64Array>();
256
+ std::size_t length = keys.ElementLength();
257
+ Napi::Array result = Napi::Array::New(env, length);
258
+ for (std::size_t i = 0; i < length; ++i)
259
+ result[i] = Napi::Boolean::New(env, native_->count(static_cast<default_key_t>(keys[i])));
260
+ return result;
347
261
  }
348
262
 
349
- Napi::Value Index::Contains(Napi::CallbackInfo const& ctx) {
263
+ Napi::Value compiledExactSearch(Napi::CallbackInfo const& ctx) {
350
264
  Napi::Env env = ctx.Env();
351
- if (ctx.Length() < 1 || !ctx[0].IsBigInt()) {
352
- Napi::TypeError::New(env, "Expects an entry identifier").ThrowAsJavaScriptException();
353
- return {};
354
- }
355
265
 
356
- Napi::BigInt key_js = ctx[0].As<Napi::BigInt>();
357
- bool lossless = true;
358
- std::uint64_t key = key_js.Uint64Value(&lossless);
359
- if (!lossless) {
360
- Napi::TypeError::New(env, "Identifier must be an unsigned integer").ThrowAsJavaScriptException();
361
- return {};
266
+ // Extracting parameters directly without additional type checks.
267
+ Napi::TypedArray dataset = ctx[0].As<Napi::TypedArray>();
268
+ Napi::ArrayBuffer datasetBuffer = dataset.ArrayBuffer();
269
+ Napi::TypedArray queries = ctx[1].As<Napi::TypedArray>();
270
+ Napi::ArrayBuffer queriesBuffer = queries.ArrayBuffer();
271
+ std::uint64_t dimensions = napi_argument_to_size(ctx[2]);
272
+ std::uint64_t wanted = napi_argument_to_size(ctx[3]);
273
+ metric_kind_t metric_kind = metric_from_name(ctx[4].As<Napi::String>().Utf8Value().c_str());
274
+
275
+ scalar_kind_t quantization;
276
+ std::size_t bytes_per_scalar;
277
+ switch (queries.TypedArrayType()) {
278
+ case napi_float64_array: quantization = scalar_kind_t::f64_k, bytes_per_scalar = 8; break;
279
+ case napi_int8_array: quantization = scalar_kind_t::i8_k, bytes_per_scalar = 1; break;
280
+ default: quantization = scalar_kind_t::f32_k, bytes_per_scalar = 4; break;
362
281
  }
363
282
 
364
- try {
365
- bool result = native_->contains(key);
366
- return Napi::Boolean::New(env, result);
367
- } catch (std::bad_alloc const&) {
283
+ metric_punned_t metric(dimensions, metric_kind, quantization);
284
+ exact_search_t search;
285
+
286
+ // Performing the exact search.
287
+ std::size_t dataset_size = dataset.ElementLength() / dimensions;
288
+ std::size_t queries_size = queries.ElementLength() / dimensions;
289
+ auto results = search( //
290
+ reinterpret_cast<byte_t const*>(datasetBuffer.Data()), //
291
+ dataset_size, //
292
+ dimensions * bytes_per_scalar, //
293
+ reinterpret_cast<byte_t const*>(queriesBuffer.Data()), //
294
+ queries_size, //
295
+ dimensions * bytes_per_scalar, //
296
+ wanted, metric);
297
+
298
+ if (!results)
368
299
  Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
369
- return {};
370
- } catch (...) {
371
- Napi::TypeError::New(env, "Search failed").ThrowAsJavaScriptException();
372
- return {};
300
+
301
+ // Constructing the result object
302
+ Napi::Array result_js = Napi::Array::New(env, 3);
303
+ Napi::BigUint64Array matches_js = Napi::BigUint64Array::New(env, queries_size * wanted);
304
+ Napi::Float32Array distances_js = Napi::Float32Array::New(env, queries_size * wanted);
305
+ Napi::BigUint64Array counts_js = Napi::BigUint64Array::New(env, queries_size);
306
+
307
+ auto matches_data = matches_js.Data();
308
+ auto distances_data = distances_js.Data();
309
+ auto counts_data = counts_js.Data();
310
+
311
+ // Export into JS buffers
312
+ for (std::size_t task_idx = 0; task_idx != queries_size; ++task_idx) {
313
+ auto result = results.at(task_idx);
314
+ counts_data[task_idx] = wanted;
315
+ for (std::size_t result_idx = 0; result_idx != wanted; ++result_idx) {
316
+ matches_data[task_idx * wanted + result_idx] = result[result_idx].offset;
317
+ distances_data[task_idx * wanted + result_idx] = result[result_idx].distance;
318
+ }
373
319
  }
320
+
321
+ result_js.Set(0u, matches_js);
322
+ result_js.Set(1u, distances_js);
323
+ result_js.Set(2u, counts_js);
324
+ return result_js;
374
325
  }
375
326
 
376
- Napi::Object InitAll(Napi::Env env, Napi::Object exports) { return Index::Init(env, exports); }
327
+ Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
328
+ exports.Set("compiledExactSearch", Napi::Function::New(env, compiledExactSearch));
329
+ return CompiledIndex::Init(env, exports);
330
+ }
377
331
 
378
332
  NODE_API_MODULE(usearch, InitAll)