usearch 2.10.3 → 2.11.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 (114) hide show
  1. package/README.md +28 -8
  2. package/binding.gyp +101 -0
  3. package/c/CMakeLists.txt +27 -0
  4. package/c/README.md +244 -0
  5. package/c/lib.cpp +459 -0
  6. package/c/test.c +386 -0
  7. package/c/usearch.h +451 -0
  8. package/cmake/clipp.cmake +7 -0
  9. package/cmake/config.cmake.in +15 -0
  10. package/cmake/emscripten.cmake +8 -0
  11. package/cmake/jemalloc.cmake +62 -0
  12. package/cmake/pkg-config.pc.in +4 -0
  13. package/cmake/usearchConfigVersion.cmake.in +20 -0
  14. package/cpp/CMakeLists.txt +14 -0
  15. package/cpp/README.md +161 -0
  16. package/cpp/bench.cpp +616 -0
  17. package/cpp/test.cpp +335 -0
  18. package/fp16/CMakeLists.txt +176 -0
  19. package/fp16/LICENSE +11 -0
  20. package/fp16/README.md +20 -0
  21. package/fp16/bench/alt-element.cc +89 -0
  22. package/fp16/bench/from-alt-array.cc +175 -0
  23. package/fp16/bench/from-ieee-array.cc +399 -0
  24. package/fp16/bench/ieee-element.cc +272 -0
  25. package/fp16/bench/to-alt-array.cc +91 -0
  26. package/fp16/bench/to-ieee-array.cc +303 -0
  27. package/fp16/cmake/DownloadGoogleBenchmark.cmake +15 -0
  28. package/fp16/cmake/DownloadGoogleTest.cmake +15 -0
  29. package/fp16/cmake/DownloadPSimd.cmake +15 -0
  30. package/fp16/configure.py +65 -0
  31. package/fp16/confu.yaml +8 -0
  32. package/fp16/include/fp16/__init__.py +0 -0
  33. package/fp16/include/fp16/avx.py +50 -0
  34. package/fp16/include/fp16/avx2.py +53 -0
  35. package/fp16/include/fp16/bitcasts.h +92 -0
  36. package/fp16/include/fp16/fp16.h +451 -0
  37. package/fp16/include/fp16/psimd.h +131 -0
  38. package/fp16/include/fp16.h +11 -0
  39. package/fp16/jni/Android.mk +6 -0
  40. package/fp16/jni/Application.mk +4 -0
  41. package/fp16/test/alt-from-fp32-value.cc +498 -0
  42. package/fp16/test/alt-to-fp32-bits.cc +263 -0
  43. package/fp16/test/alt-to-fp32-psimd.cc +145 -0
  44. package/fp16/test/alt-to-fp32-value.cc +348 -0
  45. package/fp16/test/alt-to-fp32x2-psimd.cc +245 -0
  46. package/fp16/test/bitcasts.cc +56 -0
  47. package/fp16/test/ieee-from-fp32-value.cc +523 -0
  48. package/fp16/test/ieee-to-fp32-bits.cc +333 -0
  49. package/fp16/test/ieee-to-fp32-psimd.cc +312 -0
  50. package/fp16/test/ieee-to-fp32-value.cc +420 -0
  51. package/fp16/test/ieee-to-fp32x2-psimd.cc +541 -0
  52. package/fp16/test/peachpy/alt-xmm-to-fp32-xmm-avx.cc +96 -0
  53. package/fp16/test/peachpy/alt-xmm-to-fp32-ymm-avx2.cc +96 -0
  54. package/fp16/test/peachpy/stubs.py +38 -0
  55. package/fp16/test/tables.cc +530 -0
  56. package/fp16/test/tables.h +13 -0
  57. package/fp16/third-party/THHalf.h +92 -0
  58. package/fp16/third-party/eigen-half.h +170 -0
  59. package/fp16/third-party/float16-compressor.h +80 -0
  60. package/fp16/third-party/half.hpp +2916 -0
  61. package/fp16/third-party/npy-halffloat.h +328 -0
  62. package/include/usearch/index.hpp +3838 -0
  63. package/include/usearch/index_dense.hpp +2022 -0
  64. package/include/usearch/index_plugins.hpp +2317 -0
  65. package/javascript/dist/cjs/usearch.js +5 -5
  66. package/javascript/dist/esm/usearch.js +2 -2
  67. package/javascript/dist-package-cjs.json +3 -0
  68. package/javascript/dist-package-esm.json +3 -0
  69. package/javascript/lib.cpp +350 -0
  70. package/javascript/node-gyp-build.d.ts +1 -0
  71. package/javascript/tsconfig-base.json +25 -0
  72. package/javascript/tsconfig-cjs.json +8 -0
  73. package/javascript/tsconfig-esm.json +8 -0
  74. package/javascript/usearch.test.js +84 -0
  75. package/javascript/usearch.ts +640 -0
  76. package/package.json +14 -7
  77. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  78. package/prebuilds/linux-arm64/usearch.node +0 -0
  79. package/prebuilds/linux-x64/usearch.node +0 -0
  80. package/prebuilds/win32-ia32/usearch.node +0 -0
  81. package/prebuilds/win32-x64/usearch.node +0 -0
  82. package/simsimd/.releaserc +87 -0
  83. package/simsimd/CONTRIBUTING.md +114 -0
  84. package/simsimd/Cargo.lock +709 -0
  85. package/simsimd/Cargo.toml +44 -0
  86. package/simsimd/LICENSE +201 -0
  87. package/simsimd/README.md +708 -0
  88. package/simsimd/binding.gyp +36 -0
  89. package/simsimd/build.rs +51 -0
  90. package/simsimd/c/lib.c +110 -0
  91. package/simsimd/include/simsimd/binary.h +258 -0
  92. package/simsimd/include/simsimd/dot.h +1237 -0
  93. package/simsimd/include/simsimd/geospatial.h +37 -0
  94. package/simsimd/include/simsimd/probability.h +563 -0
  95. package/simsimd/include/simsimd/simsimd.h +1129 -0
  96. package/simsimd/include/simsimd/spatial.h +1019 -0
  97. package/simsimd/include/simsimd/types.h +309 -0
  98. package/simsimd/javascript/bench.js +127 -0
  99. package/simsimd/javascript/dist-package-cjs.json +3 -0
  100. package/simsimd/javascript/dist-package-esm.json +3 -0
  101. package/simsimd/javascript/fallback.ts +203 -0
  102. package/simsimd/javascript/lib.c +107 -0
  103. package/simsimd/javascript/node-gyp-build.d.ts +1 -0
  104. package/simsimd/javascript/simsimd.ts +135 -0
  105. package/simsimd/javascript/test.mjs +179 -0
  106. package/simsimd/javascript/tsconfig-base.json +37 -0
  107. package/simsimd/javascript/tsconfig-cjs.json +8 -0
  108. package/simsimd/javascript/tsconfig-esm.json +8 -0
  109. package/simsimd/package-lock.json +1709 -0
  110. package/simsimd/package.json +61 -0
  111. package/simsimd/rust/benches/cosine.rs +27 -0
  112. package/simsimd/rust/benches/native.rs +30 -0
  113. package/simsimd/rust/benches/sqeuclidean.rs +27 -0
  114. package/simsimd/rust/lib.rs +778 -0
@@ -40,8 +40,8 @@ var _Index_compiledIndex;
40
40
  Object.defineProperty(exports, "__esModule", { value: true });
41
41
  exports.Index = exports.BatchMatches = exports.Matches = exports.ScalarKind = exports.MetricKind = void 0;
42
42
  const node_gyp_build_1 = __importDefault(require("node-gyp-build"));
43
- const path = __importStar(require("node:path"));
44
- const node_fs_1 = require("node:fs");
43
+ const path = __importStar(require("path"));
44
+ const fs_1 = require("fs");
45
45
  const bindings_1 = require("bindings");
46
46
  const compiled = (0, node_gyp_build_1.default)(getBuildDir(getDirName()));
47
47
  /**
@@ -507,14 +507,14 @@ module.exports = {
507
507
  };
508
508
  // utility functions to help find native builds
509
509
  function getBuildDir(dir) {
510
- if ((0, node_fs_1.existsSync)(path.join(dir, "build")))
510
+ if ((0, fs_1.existsSync)(path.join(dir, "build")))
511
511
  return dir;
512
- if ((0, node_fs_1.existsSync)(path.join(dir, "prebuilds")))
512
+ if ((0, fs_1.existsSync)(path.join(dir, "prebuilds")))
513
513
  return dir;
514
514
  if (path.basename(dir) === ".next") {
515
515
  // special case for next.js on custom node (not vercel)
516
516
  const sideways = path.join(dir, "..", "node_modules", "usearch");
517
- if ((0, node_fs_1.existsSync)(sideways))
517
+ if ((0, fs_1.existsSync)(sideways))
518
518
  return getBuildDir(sideways);
519
519
  }
520
520
  if (dir === "/")
@@ -1,6 +1,6 @@
1
1
  import build from "node-gyp-build";
2
- import * as path from "node:path";
3
- import { existsSync } from "node:fs";
2
+ import * as path from "path";
3
+ import { existsSync } from "fs";
4
4
  import { getFileName, getRoot } from "bindings";
5
5
  const compiled = build(getBuildDir(getDirName()));
6
6
  /**
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -0,0 +1,350 @@
1
+ /**
2
+ * @file javascript.cpp
3
+ * @author Ash Vardanian
4
+ * @brief JavaScript bindings for Unum USearch.
5
+ * @date 2023-04-26
6
+ *
7
+ * @copyright Copyright (c) 2023
8
+ *
9
+ * @see NodeJS docs: https://nodejs.org/api/addons.html#hello-world
10
+ *
11
+ */
12
+ #include <new> // `std::bad_alloc`
13
+
14
+ #define NAPI_CPP_EXCEPTIONS
15
+ #include <napi.h>
16
+ #include <node_api.h>
17
+
18
+ #include <usearch/index_dense.hpp>
19
+
20
+ using namespace unum::usearch;
21
+ using namespace unum;
22
+
23
+ class CompiledIndex : public Napi::ObjectWrap<CompiledIndex> {
24
+ public:
25
+ static Napi::Object Init(Napi::Env env, Napi::Object exports);
26
+ CompiledIndex(Napi::CallbackInfo const& ctx);
27
+
28
+ private:
29
+ Napi::Value GetDimensions(Napi::CallbackInfo const& ctx);
30
+ Napi::Value GetSize(Napi::CallbackInfo const& ctx);
31
+ Napi::Value GetCapacity(Napi::CallbackInfo const& ctx);
32
+ Napi::Value GetConnectivity(Napi::CallbackInfo const& ctx);
33
+
34
+ void Save(Napi::CallbackInfo const& ctx);
35
+ void Load(Napi::CallbackInfo const& ctx);
36
+ void View(Napi::CallbackInfo const& ctx);
37
+
38
+ void Add(Napi::CallbackInfo const& ctx);
39
+ Napi::Value Search(Napi::CallbackInfo const& ctx);
40
+ Napi::Value Remove(Napi::CallbackInfo const& ctx);
41
+ Napi::Value Contains(Napi::CallbackInfo const& ctx);
42
+ Napi::Value Count(Napi::CallbackInfo const& ctx);
43
+
44
+ std::unique_ptr<index_dense_t> native_;
45
+ };
46
+
47
+ Napi::Object CompiledIndex::Init(Napi::Env env, Napi::Object exports) {
48
+ Napi::Function func = DefineClass( //
49
+ env, "CompiledIndex",
50
+ {
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),
63
+ });
64
+
65
+ Napi::FunctionReference* constructor = new Napi::FunctionReference();
66
+ *constructor = Napi::Persistent(func);
67
+ env.SetInstanceData(constructor);
68
+
69
+ exports.Set("CompiledIndex", func);
70
+ return exports;
71
+ }
72
+
73
+ std::size_t napi_argument_to_size(Napi::Value v) {
74
+ return static_cast<std::size_t>(v.As<Napi::Number>().DoubleValue());
75
+ }
76
+
77
+ CompiledIndex::CompiledIndex(Napi::CallbackInfo const& ctx) : Napi::ObjectWrap<CompiledIndex>(ctx) {
78
+
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();
87
+
88
+ metric_punned_t metric(dimensions, metric_kind, quantization);
89
+ if (metric.missing()) {
90
+ Napi::TypeError::New(ctx.Env(), "Failed to initialize the metric!").ThrowAsJavaScriptException();
91
+ return;
92
+ }
93
+
94
+ index_dense_config_t config(connectivity, expansion_add, expansion_search);
95
+ config.multi = multi;
96
+ native_.reset(new index_dense_t(index_dense_t::make(metric, config)));
97
+ if (!native_)
98
+ Napi::Error::New(ctx.Env(), "Out of memory!").ThrowAsJavaScriptException();
99
+ }
100
+
101
+ Napi::Value CompiledIndex::GetDimensions(Napi::CallbackInfo const& ctx) {
102
+ return Napi::Number::New(ctx.Env(), static_cast<std::uint64_t>(native_->dimensions()));
103
+ }
104
+ Napi::Value CompiledIndex::GetConnectivity(Napi::CallbackInfo const& ctx) {
105
+ return Napi::Number::New(ctx.Env(), static_cast<std::uint64_t>(native_->connectivity()));
106
+ }
107
+ Napi::Value CompiledIndex::GetSize(Napi::CallbackInfo const& ctx) {
108
+ return Napi::Number::New(ctx.Env(), static_cast<std::uint64_t>(native_->size()));
109
+ }
110
+ Napi::Value CompiledIndex::GetCapacity(Napi::CallbackInfo const& ctx) {
111
+ return Napi::Number::New(ctx.Env(), static_cast<std::uint64_t>(native_->capacity()));
112
+ }
113
+
114
+ void CompiledIndex::Save(Napi::CallbackInfo const& ctx) {
115
+ try {
116
+ std::string path = ctx[0].As<Napi::String>();
117
+ auto result = native_->save(path.c_str());
118
+ if (!result)
119
+ Napi::TypeError::New(ctx.Env(), result.error.release()).ThrowAsJavaScriptException();
120
+
121
+ } catch (...) {
122
+ Napi::TypeError::New(ctx.Env(), "Serialization failed").ThrowAsJavaScriptException();
123
+ }
124
+ }
125
+
126
+ void CompiledIndex::Load(Napi::CallbackInfo const& ctx) {
127
+ try {
128
+ std::string path = ctx[0].As<Napi::String>();
129
+ auto result = native_->load(path.c_str());
130
+ if (!result)
131
+ Napi::TypeError::New(ctx.Env(), result.error.release()).ThrowAsJavaScriptException();
132
+
133
+ } catch (...) {
134
+ Napi::TypeError::New(ctx.Env(), "Loading failed").ThrowAsJavaScriptException();
135
+ }
136
+ }
137
+
138
+ void CompiledIndex::View(Napi::CallbackInfo const& ctx) {
139
+
140
+ try {
141
+ std::string path = ctx[0].As<Napi::String>();
142
+ auto result = native_->view(path.c_str());
143
+ if (!result)
144
+ Napi::TypeError::New(ctx.Env(), result.error.release()).ThrowAsJavaScriptException();
145
+
146
+ } catch (...) {
147
+ Napi::TypeError::New(ctx.Env(), "Memory-mapping failed").ThrowAsJavaScriptException();
148
+ }
149
+ }
150
+
151
+ void CompiledIndex::Add(Napi::CallbackInfo const& ctx) {
152
+ // Extract keys and vectors from arguments
153
+ Napi::BigUint64Array keys = ctx[0].As<Napi::BigUint64Array>();
154
+ std::size_t tasks = keys.ElementLength();
155
+
156
+ // Ensure there is enough capacity
157
+ if (native_->size() + tasks >= native_->capacity())
158
+ native_->reserve(ceil2(native_->size() + tasks));
159
+
160
+ // Create an instance of the executor with the default number of threads
161
+ auto run_parallel = [&](auto vectors) {
162
+ executor_stl_t executor;
163
+ executor.fixed(tasks, [&](std::size_t /*thread_idx*/, std::size_t task_idx) {
164
+ native_->add(static_cast<default_key_t>(keys[task_idx]), vectors + task_idx * native_->dimensions());
165
+ });
166
+ };
167
+
168
+ Napi::TypedArray vectors = ctx[1].As<Napi::TypedArray>();
169
+ if (vectors.TypedArrayType() == napi_float32_array) {
170
+ run_parallel(vectors.As<Napi::Float32Array>().Data());
171
+ } else if (vectors.TypedArrayType() == napi_float64_array) {
172
+ run_parallel(vectors.As<Napi::Float64Array>().Data());
173
+ } else if (vectors.TypedArrayType() == napi_int8_array) {
174
+ run_parallel(vectors.As<Napi::Int8Array>().Data());
175
+ } else {
176
+ Napi::TypeError::New(ctx.Env(),
177
+ "Unsupported TypedArray. Supported types are Float32Array, Float64Array, and Int8Array.")
178
+ .ThrowAsJavaScriptException();
179
+ }
180
+ }
181
+
182
+ Napi::Value CompiledIndex::Search(Napi::CallbackInfo const& ctx) {
183
+ Napi::Env env = ctx.Env();
184
+ Napi::TypedArray queries = ctx[0].As<Napi::TypedArray>();
185
+ std::size_t tasks = queries.ElementLength() / native_->dimensions();
186
+ std::size_t wanted = napi_argument_to_size(ctx[1]);
187
+
188
+ auto run_parallel = [&](auto vectors) -> Napi::Value {
189
+ Napi::Array result_js = Napi::Array::New(env, 3);
190
+ Napi::BigUint64Array matches_js = Napi::BigUint64Array::New(env, tasks * wanted);
191
+ Napi::Float32Array distances_js = Napi::Float32Array::New(env, tasks * wanted);
192
+ Napi::BigUint64Array counts_js = Napi::BigUint64Array::New(env, tasks);
193
+
194
+ auto matches_data = matches_js.Data();
195
+ auto distances_data = distances_js.Data();
196
+ auto counts_data = counts_js.Data();
197
+
198
+ try {
199
+ bool failed = false;
200
+ executor_stl_t executor;
201
+ executor.fixed(tasks, [&](std::size_t /*thread_idx*/, std::size_t task_idx) {
202
+ auto result = native_->search(vectors + task_idx * native_->dimensions(), wanted);
203
+ if (!result) {
204
+ failed = true;
205
+ Napi::TypeError::New(env, result.error.release()).ThrowAsJavaScriptException();
206
+ } else {
207
+ counts_data[task_idx] = result.dump_to(matches_data + task_idx * native_->dimensions(),
208
+ distances_data + task_idx * native_->dimensions());
209
+ }
210
+ });
211
+
212
+ if (failed)
213
+ return env.Null();
214
+
215
+ } catch (std::bad_alloc const&) {
216
+ Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
217
+ return env.Null();
218
+
219
+ } catch (...) {
220
+ Napi::TypeError::New(env, "Search failed").ThrowAsJavaScriptException();
221
+ return env.Null();
222
+ }
223
+
224
+ result_js.Set(0u, matches_js);
225
+ result_js.Set(1u, distances_js);
226
+ result_js.Set(2u, counts_js);
227
+ return result_js;
228
+ };
229
+
230
+ if (queries.TypedArrayType() == napi_float32_array) {
231
+ return run_parallel(queries.As<Napi::Float32Array>().Data());
232
+ } else if (queries.TypedArrayType() == napi_float64_array) {
233
+ return run_parallel(queries.As<Napi::Float64Array>().Data());
234
+ } else if (queries.TypedArrayType() == napi_int8_array) {
235
+ return run_parallel(queries.As<Napi::Int8Array>().Data());
236
+ } else {
237
+ Napi::TypeError::New(env,
238
+ "Unsupported TypedArray. Supported types are Float32Array, Float64Array, and Int8Array.")
239
+ .ThrowAsJavaScriptException();
240
+ return env.Null();
241
+ }
242
+ }
243
+
244
+ Napi::Value CompiledIndex::Remove(Napi::CallbackInfo const& ctx) {
245
+ Napi::Env env = ctx.Env();
246
+ Napi::BigUint64Array keys = ctx[0].As<Napi::BigUint64Array>();
247
+ std::size_t length = keys.ElementLength();
248
+ Napi::Array result = Napi::Array::New(env, length);
249
+ for (std::size_t i = 0; i < length; ++i) {
250
+ result[i] = Napi::Number::New(env, native_->remove(static_cast<default_key_t>(keys[i])).completed);
251
+ }
252
+ return result;
253
+ }
254
+
255
+ Napi::Value CompiledIndex::Contains(Napi::CallbackInfo const& ctx) {
256
+ Napi::Env env = ctx.Env();
257
+ Napi::BigUint64Array keys = ctx[0].As<Napi::BigUint64Array>();
258
+ std::size_t length = keys.ElementLength();
259
+ Napi::Array result = Napi::Array::New(env, length);
260
+ for (std::size_t i = 0; i < length; ++i)
261
+ result[i] = Napi::Boolean::New(env, native_->contains(static_cast<default_key_t>(keys[i])));
262
+ return result;
263
+ }
264
+
265
+ Napi::Value CompiledIndex::Count(Napi::CallbackInfo const& ctx) {
266
+ Napi::Env env = ctx.Env();
267
+ Napi::BigUint64Array keys = ctx[0].As<Napi::BigUint64Array>();
268
+ std::size_t length = keys.ElementLength();
269
+ Napi::Array result = Napi::Array::New(env, length);
270
+ for (std::size_t i = 0; i < length; ++i)
271
+ result[i] = Napi::Boolean::New(env, native_->count(static_cast<default_key_t>(keys[i])));
272
+ return result;
273
+ }
274
+
275
+ Napi::Value exactSearch(Napi::CallbackInfo const& ctx) {
276
+ Napi::Env env = ctx.Env();
277
+
278
+ // Extracting parameters directly without additional type checks.
279
+ Napi::TypedArray dataset = ctx[0].As<Napi::TypedArray>();
280
+ Napi::ArrayBuffer datasetBuffer = dataset.ArrayBuffer();
281
+ Napi::TypedArray queries = ctx[1].As<Napi::TypedArray>();
282
+ Napi::ArrayBuffer queriesBuffer = queries.ArrayBuffer();
283
+ std::uint64_t dimensions = napi_argument_to_size(ctx[2]);
284
+ std::uint64_t wanted = napi_argument_to_size(ctx[3]);
285
+ metric_kind_t metric_kind = metric_from_name(ctx[4].As<Napi::String>().Utf8Value().c_str());
286
+
287
+ scalar_kind_t quantization;
288
+ std::size_t bytes_per_scalar;
289
+ switch (queries.TypedArrayType()) {
290
+ case napi_float64_array: quantization = scalar_kind_t::f64_k, bytes_per_scalar = 8; break;
291
+ case napi_int8_array: quantization = scalar_kind_t::i8_k, bytes_per_scalar = 1; break;
292
+ default: quantization = scalar_kind_t::f32_k, bytes_per_scalar = 4; break;
293
+ }
294
+
295
+ metric_punned_t metric(dimensions, metric_kind, quantization);
296
+ if (!metric) {
297
+ Napi::TypeError::New(env, "Failed to initialize the metric!").ThrowAsJavaScriptException();
298
+ return env.Null();
299
+ }
300
+
301
+ executor_default_t executor;
302
+ exact_search_t search;
303
+
304
+ // Performing the exact search.
305
+ std::size_t dataset_size = dataset.ElementLength() / dimensions;
306
+ std::size_t queries_size = queries.ElementLength() / dimensions;
307
+ auto results = search( //
308
+ reinterpret_cast<byte_t const*>(datasetBuffer.Data()), //
309
+ dataset_size, //
310
+ dimensions * bytes_per_scalar, //
311
+ reinterpret_cast<byte_t const*>(queriesBuffer.Data()), //
312
+ queries_size, //
313
+ dimensions * bytes_per_scalar, //
314
+ wanted, metric, executor);
315
+
316
+ if (!results)
317
+ Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
318
+
319
+ // Constructing the result object
320
+ Napi::Array result_js = Napi::Array::New(env, 3);
321
+ Napi::BigUint64Array matches_js = Napi::BigUint64Array::New(env, queries_size * wanted);
322
+ Napi::Float32Array distances_js = Napi::Float32Array::New(env, queries_size * wanted);
323
+ Napi::BigUint64Array counts_js = Napi::BigUint64Array::New(env, queries_size);
324
+
325
+ auto matches_data = matches_js.Data();
326
+ auto distances_data = distances_js.Data();
327
+ auto counts_data = counts_js.Data();
328
+
329
+ // Export into JS buffers
330
+ for (std::size_t task_idx = 0; task_idx != queries_size; ++task_idx) {
331
+ auto result = results.at(task_idx);
332
+ counts_data[task_idx] = wanted;
333
+ for (std::size_t result_idx = 0; result_idx != wanted; ++result_idx) {
334
+ matches_data[task_idx * wanted + result_idx] = result[result_idx].offset;
335
+ distances_data[task_idx * wanted + result_idx] = result[result_idx].distance;
336
+ }
337
+ }
338
+
339
+ result_js.Set(0u, matches_js);
340
+ result_js.Set(1u, distances_js);
341
+ result_js.Set(2u, counts_js);
342
+ return result_js;
343
+ }
344
+
345
+ Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
346
+ exports.Set("exactSearch", Napi::Function::New(env, exactSearch));
347
+ return CompiledIndex::Init(env, exports);
348
+ }
349
+
350
+ NODE_API_MODULE(usearch, InitAll)
@@ -0,0 +1 @@
1
+ declare module "node-gyp-build";
@@ -0,0 +1,25 @@
1
+ {
2
+ "compileOnSave": false,
3
+ "exclude": ["node_modules", "dist"],
4
+ "include": ["node-gyp-build.d.ts", "usearch.ts"],
5
+ "compilerOptions": {
6
+ "allowJs": true,
7
+ "allowSyntheticDefaultImports": true,
8
+ "baseUrl": ".",
9
+ "declaration": true,
10
+ "esModuleInterop": true,
11
+ "inlineSourceMap": false,
12
+ "lib": ["esnext"],
13
+ "listEmittedFiles": false,
14
+ "listFiles": false,
15
+ "moduleResolution": "node",
16
+ "noFallthroughCasesInSwitch": true,
17
+ "pretty": true,
18
+ "resolveJsonModule": true,
19
+ "rootDir": ".",
20
+ "skipLibCheck": true,
21
+ "strict": true,
22
+ "traceResolution": false,
23
+ "types": ["node","./node-gyp-build.d.ts"]
24
+ }
25
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "./tsconfig-base.json",
3
+ "compilerOptions": {
4
+ "module": "commonjs",
5
+ "outDir": "dist/cjs",
6
+ "target": "es2015"
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "./tsconfig-base.json",
3
+ "compilerOptions": {
4
+ "module": "esnext",
5
+ "outDir": "dist/esm",
6
+ "target": "esnext"
7
+ }
8
+ }
@@ -0,0 +1,84 @@
1
+ const test = require('node:test');
2
+ const assert = require('node:assert');
3
+ const usearch = require('./dist/cjs/usearch.js');
4
+
5
+ function assertAlmostEqual(actual, expected, tolerance = 1e-6) {
6
+ const lowerBound = expected - tolerance;
7
+ const upperBound = expected + tolerance;
8
+ assert(
9
+ actual >= lowerBound && actual <= upperBound,
10
+ `Expected ${actual} to be almost equal to ${expected}`
11
+ );
12
+ }
13
+
14
+
15
+ test('Single-entry operations', () => {
16
+ const index = new usearch.Index(2, 'l2sq');
17
+
18
+ assert.equal(index.connectivity(), 16, 'connectivity should be 16');
19
+ assert.equal(index.dimensions(), 2, 'dimensions should be 2');
20
+ assert.equal(index.size(), 0, 'initial size should be 0');
21
+
22
+ index.add(15n, new Float32Array([10, 20]));
23
+ index.add(16n, new Float32Array([10, 25]));
24
+
25
+ assert.equal(index.size(), 2, 'size after adding elements should be 2');
26
+ assert.equal(index.contains(15), true, 'entry must be present after insertion');
27
+
28
+ const results = index.search(new Float32Array([13, 14]), 2);
29
+
30
+ assert.deepEqual(results.keys, new BigUint64Array([15n, 16n]), 'keys should be 15 and 16');
31
+ assert.deepEqual(results.distances, new Float32Array([45, 130]), 'distances should be 45 and 130');
32
+ });
33
+
34
+ test('Batch operations', () => {
35
+ const indexBatch = new usearch.Index(2, 'l2sq');
36
+
37
+ const keys = [15n, 16n];
38
+ const vectors = [new Float32Array([10, 20]), new Float32Array([10, 25])];
39
+
40
+ indexBatch.add(keys, vectors);
41
+ assert.equal(indexBatch.size(), 2, 'size after adding batch should be 2');
42
+
43
+ const results = indexBatch.search(new Float32Array([13, 14]), 2);
44
+
45
+ assert.deepEqual(results.keys, new BigUint64Array([15n, 16n]), 'keys should be 15 and 16');
46
+ assert.deepEqual(results.distances, new Float32Array([45, 130]), 'distances should be 45 and 130');
47
+ });
48
+
49
+ test("Expected results", () => {
50
+ var index = new usearch.Index({
51
+ metric: "l2sq",
52
+ connectivity: 16,
53
+ dimensions: 3,
54
+ });
55
+ index.add(42n, new Float32Array([0.2, 0.6, 0.4]));
56
+ var results = index.search(new Float32Array([0.2, 0.6, 0.4]), 10);
57
+
58
+ assert.equal(index.size(), 1);
59
+ assert.deepEqual(results.keys, new BigUint64Array([42n]));
60
+ assertAlmostEqual(results.distances[0], new Float32Array([0]));
61
+ });
62
+
63
+
64
+
65
+ test('Operations with invalid values', () => {
66
+ const indexBatch = new usearch.Index(2, 'l2sq');
67
+
68
+ const keys = [NaN, 16n];
69
+ const vectors = [new Float32Array([10, 30]), new Float32Array([1, 5])];
70
+
71
+ try {
72
+ indexBatch.add(keys, vectors);
73
+ throw new Error('indexBatch.add should have thrown an error.');
74
+ } catch (err) {
75
+ assert.equal(err.message, 'All keys must be positive integers or bigints.');
76
+ }
77
+
78
+ try {
79
+ indexBatch.search(NaN, 2);
80
+ throw new Error('indexBatch.search should have thrown an error.');
81
+ } catch (err) {
82
+ assert.equal(err.message, 'Vectors must be a TypedArray or an array of arrays.');
83
+ }
84
+ });