bare-usearch 0.1.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/CMakeLists.txt ADDED
@@ -0,0 +1,70 @@
1
+ cmake_minimum_required(VERSION 3.25)
2
+
3
+ find_package(cmake-bare REQUIRED PATHS node_modules/cmake-bare)
4
+ find_package(cmake-napi REQUIRED PATHS node_modules/cmake-napi)
5
+
6
+ project(bare_usearch C CXX)
7
+
8
+ # usearch requires C++ exceptions - remove -fno-exceptions if present
9
+ string(REPLACE "-fno-exceptions" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
10
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions")
11
+
12
+ # Remove NDEBUG to avoid noexcept declarations that conflict with exception handling
13
+ string(REPLACE "-DNDEBUG" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
14
+
15
+ set(usearch_sources
16
+ binding.c
17
+ vendor/usearch/c/lib.cpp
18
+ usearch_wrapper.cpp
19
+ )
20
+
21
+ set(usearch_includes
22
+ vendor/usearch/include
23
+ vendor/usearch/c
24
+ vendor/usearch/fp16/include
25
+ )
26
+
27
+ set(usearch_defines
28
+ USEARCH_USE_OPENMP=0
29
+ USEARCH_USE_SIMSIMD=0
30
+ USEARCH_USE_FP16LIB=1
31
+ USEARCH_CAN_COMPILE_FP16=0
32
+ USEARCH_CAN_COMPILE_FLOAT16=0
33
+ USEARCH_CAN_COMPILE_BF16=0
34
+ USEARCH_CAN_COMPILE_BFLOAT16=0
35
+ )
36
+
37
+ # Create the Bare addon
38
+ add_bare_module(bare_usearch_bare)
39
+
40
+ target_sources(${bare_usearch_bare} PRIVATE ${usearch_sources})
41
+ target_include_directories(${bare_usearch_bare} PRIVATE ${usearch_includes})
42
+ target_compile_definitions(${bare_usearch_bare} PRIVATE ${usearch_defines})
43
+ set_target_properties(${bare_usearch_bare} PROPERTIES
44
+ CXX_STANDARD 11
45
+ CXX_STANDARD_REQUIRED ON
46
+ )
47
+ target_compile_options(${bare_usearch_bare} PRIVATE $<$<COMPILE_LANGUAGE:CXX>:-fexceptions>)
48
+
49
+ # Create the Node.js NAPI addon
50
+ add_napi_module(bare_usearch_node)
51
+
52
+ target_sources(${bare_usearch_node} PRIVATE ${usearch_sources})
53
+ target_compile_definitions(${bare_usearch_node} PRIVATE
54
+ ${usearch_defines}
55
+ NAPI_VERSION=9
56
+ )
57
+ target_include_directories(${bare_usearch_node} PRIVATE ${usearch_includes})
58
+ set_target_properties(${bare_usearch_node} PROPERTIES
59
+ CXX_STANDARD 11
60
+ CXX_STANDARD_REQUIRED ON
61
+ )
62
+ target_compile_options(${bare_usearch_node} PRIVATE $<$<COMPILE_LANGUAGE:CXX>:-fexceptions>)
63
+
64
+ resolve_node_module(bare-compat-napi compat)
65
+
66
+ target_include_directories(
67
+ ${bare_usearch_node}
68
+ PRIVATE
69
+ "${compat}/include"
70
+ )
package/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # bare-usearch
2
+
3
+ Native [USearch](https://github.com/unum-cloud/usearch) bindings for [Bare](https://github.com/holepunchto/bare).
4
+
5
+ Fast approximate nearest neighbor search for high-dimensional vectors.
6
+
7
+ ## Requirements
8
+
9
+ - CMake 3.25+
10
+ - C/C++ compiler (clang, gcc, or MSVC)
11
+ - Node.js (for npm/cmake-bare)
12
+ - Bare runtime
13
+
14
+ ## Building
15
+
16
+ Clone with submodules:
17
+
18
+ ```bash
19
+ git clone --recursive https://github.com/CameronTofer/bare-usearch
20
+ cd bare-usearch
21
+ ```
22
+
23
+ Or if already cloned:
24
+
25
+ ```bash
26
+ git submodule update --init --recursive
27
+ ```
28
+
29
+ Install dependencies and build:
30
+
31
+ ```bash
32
+ npm install
33
+ npm run build
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ ```javascript
39
+ const { Index } = require('bare-usearch')
40
+
41
+ // Create index with 128-dimensional vectors
42
+ const index = new Index({
43
+ dimensions: 128,
44
+ metric: 'cos' // cosine similarity
45
+ })
46
+
47
+ // Add vectors (key, vector)
48
+ index.add(1, new Float32Array([0.1, 0.2, ...]))
49
+ index.add(2, new Float32Array([0.3, 0.4, ...]))
50
+
51
+ // Search for k nearest neighbors
52
+ const results = index.search(queryVector, 10)
53
+ console.log(results.keys) // [2, 1, ...]
54
+ console.log(results.distances) // Float32Array
55
+
56
+ // Persistence
57
+ index.save('index.usearch')
58
+ index.load('index.usearch')
59
+
60
+ // Cleanup
61
+ index.free()
62
+ ```
63
+
64
+ See `examples/` for more:
65
+ - `basic.js` - Basic usage with add/search/remove
66
+ - `persistence.js` - Saving and loading indexes
67
+ - `topics.js` - Topic extraction using vector similarity
68
+
69
+ Run examples with:
70
+ ```bash
71
+ bare examples/basic.js
72
+ ```
73
+
74
+ ## Testing
75
+
76
+ ```bash
77
+ npm test # bare
78
+ npm run test:node # node
79
+ ```
80
+
81
+ ## API Reference
82
+
83
+ ### Index
84
+
85
+ ```javascript
86
+ new Index(options?)
87
+ ```
88
+
89
+ | Option | Type | Default | Description |
90
+ |--------|------|---------|-------------|
91
+ | `dimensions` | number | required | Vector dimensionality |
92
+ | `metric` | string | `'cos'` | Distance metric |
93
+ | `quantization` | string | `'f32'` | Scalar quantization |
94
+ | `connectivity` | number | auto | Graph connectivity (M) |
95
+ | `multi` | boolean | `false` | Allow multiple vectors per key |
96
+
97
+ **Metrics:** `'cos'`, `'ip'` (inner product), `'l2'` (L2 squared), `'hamming'`, `'jaccard'`
98
+
99
+ **Quantization:** `'f32'`, `'f16'`, `'i8'`
100
+
101
+ **Properties:**
102
+
103
+ - `size` - Number of vectors in index
104
+ - `dimensions` - Vector dimensionality
105
+ - `capacity` - Current capacity
106
+
107
+ **Methods:**
108
+
109
+ - `add(key, vector)` - Add a vector with numeric key
110
+ - `search(query, k)` - Find k nearest neighbors, returns `{ keys, distances, count }`
111
+ - `remove(key)` - Remove vector by key
112
+ - `contains(key)` - Check if key exists
113
+ - `get(key)` - Retrieve vector by key (returns Float32Array or null)
114
+ - `reserve(capacity)` - Pre-allocate space for vectors
115
+ - `save(path)` - Save index to file
116
+ - `load(path)` - Load index from file
117
+ - `view(path)` - Memory-map index from file (read-only)
118
+ - `clear()` - Remove all vectors
119
+ - `free()` - Release resources
120
+
121
+ ## Platform Support
122
+
123
+ | Platform | Architecture |
124
+ |----------|--------------|
125
+ | macOS | arm64, x64 |
126
+ | Linux | x64, arm64 |
127
+ | Windows | x64, arm64 |
128
+ | Android | arm64, arm, x64, ia32 |
129
+ | iOS | arm64, x64 (simulator) |
130
+
131
+ ## License
132
+
133
+ MIT
package/binding.c ADDED
@@ -0,0 +1,529 @@
1
+ #include <js.h>
2
+ #include <bare.h>
3
+ #include <string.h>
4
+ #include <stdlib.h>
5
+ #include "usearch.h"
6
+
7
+ // Exception-safe wrappers (defined in usearch_wrapper.cpp)
8
+ extern usearch_index_t usearch_init_safe(usearch_init_options_t* options, usearch_error_t* error);
9
+ extern void usearch_free_safe(usearch_index_t index, usearch_error_t* error);
10
+ extern size_t usearch_size_safe(usearch_index_t index, usearch_error_t* error);
11
+ extern size_t usearch_capacity_safe(usearch_index_t index, usearch_error_t* error);
12
+ extern size_t usearch_dimensions_safe(usearch_index_t index, usearch_error_t* error);
13
+ extern void usearch_reserve_safe(usearch_index_t index, size_t capacity, usearch_error_t* error);
14
+ extern void usearch_add_safe(usearch_index_t index, usearch_key_t key, void const* vector,
15
+ usearch_scalar_kind_t kind, usearch_error_t* error);
16
+ extern size_t usearch_search_safe(usearch_index_t index, void const* query, usearch_scalar_kind_t kind,
17
+ size_t count, usearch_key_t* keys, usearch_distance_t* distances,
18
+ usearch_error_t* error);
19
+ extern size_t usearch_remove_safe(usearch_index_t index, usearch_key_t key, usearch_error_t* error);
20
+ extern bool usearch_contains_safe(usearch_index_t index, usearch_key_t key, usearch_error_t* error);
21
+ extern size_t usearch_get_safe(usearch_index_t index, usearch_key_t key, size_t count,
22
+ void* vector, usearch_scalar_kind_t kind, usearch_error_t* error);
23
+ extern void usearch_save_safe(usearch_index_t index, char const* path, usearch_error_t* error);
24
+ extern void usearch_load_safe(usearch_index_t index, char const* path, usearch_error_t* error);
25
+ extern void usearch_view_safe(usearch_index_t index, char const* path, usearch_error_t* error);
26
+ extern void usearch_clear_safe(usearch_index_t index, usearch_error_t* error);
27
+
28
+ // Helper to throw JS error from usearch error
29
+ static void throw_if_error(js_env_t *env, usearch_error_t error) {
30
+ if (error) {
31
+ js_throw_error(env, NULL, error);
32
+ }
33
+ }
34
+
35
+ // Create a new index
36
+ static js_value_t *fn_init(js_env_t *env, js_callback_info_t *info) {
37
+ size_t argc = 1;
38
+ js_value_t *argv[1];
39
+ js_get_callback_info(env, info, &argc, argv, NULL, NULL);
40
+
41
+ usearch_init_options_t opts;
42
+ memset(&opts, 0, sizeof(opts));
43
+ opts.metric_kind = usearch_metric_cos_k;
44
+ opts.quantization = usearch_scalar_f32_k;
45
+ opts.dimensions = 0;
46
+ opts.connectivity = 16; // default M value for HNSW
47
+ opts.expansion_add = 128; // ef_construction
48
+ opts.expansion_search = 64; // ef
49
+ opts.multi = false;
50
+
51
+ if (argc > 0) {
52
+ js_value_t *val;
53
+ js_value_type_t val_type;
54
+
55
+ // dimensions (required) - check type
56
+ if (js_get_named_property(env, argv[0], "dimensions", &val) == 0) {
57
+ js_typeof(env, val, &val_type);
58
+ if (val_type == js_number) {
59
+ uint32_t dim;
60
+ js_get_value_uint32(env, val, &dim);
61
+ opts.dimensions = dim;
62
+ }
63
+ }
64
+
65
+ // metric - check if property exists AND is a string
66
+ if (js_get_named_property(env, argv[0], "metric", &val) == 0) {
67
+ js_typeof(env, val, &val_type);
68
+ if (val_type == js_string) {
69
+ utf8_t metric[32];
70
+ size_t len;
71
+ js_get_value_string_utf8(env, val, metric, sizeof(metric), &len);
72
+ if (strcmp((char *)metric, "cos") == 0 || strcmp((char *)metric, "cosine") == 0) {
73
+ opts.metric_kind = usearch_metric_cos_k;
74
+ } else if (strcmp((char *)metric, "ip") == 0 || strcmp((char *)metric, "inner") == 0) {
75
+ opts.metric_kind = usearch_metric_ip_k;
76
+ } else if (strcmp((char *)metric, "l2sq") == 0 || strcmp((char *)metric, "l2") == 0) {
77
+ opts.metric_kind = usearch_metric_l2sq_k;
78
+ } else if (strcmp((char *)metric, "hamming") == 0) {
79
+ opts.metric_kind = usearch_metric_hamming_k;
80
+ } else if (strcmp((char *)metric, "jaccard") == 0) {
81
+ opts.metric_kind = usearch_metric_jaccard_k;
82
+ }
83
+ }
84
+ }
85
+
86
+ // quantization - check if property exists AND is a string
87
+ if (js_get_named_property(env, argv[0], "quantization", &val) == 0) {
88
+ js_typeof(env, val, &val_type);
89
+ if (val_type == js_string) {
90
+ utf8_t quant[16];
91
+ size_t len;
92
+ js_get_value_string_utf8(env, val, quant, sizeof(quant), &len);
93
+ if (strcmp((char *)quant, "f32") == 0) {
94
+ opts.quantization = usearch_scalar_f32_k;
95
+ } else if (strcmp((char *)quant, "f16") == 0) {
96
+ opts.quantization = usearch_scalar_f16_k;
97
+ } else if (strcmp((char *)quant, "i8") == 0) {
98
+ opts.quantization = usearch_scalar_i8_k;
99
+ }
100
+ }
101
+ }
102
+
103
+ // connectivity - check type
104
+ if (js_get_named_property(env, argv[0], "connectivity", &val) == 0) {
105
+ js_typeof(env, val, &val_type);
106
+ if (val_type == js_number) {
107
+ uint32_t conn;
108
+ js_get_value_uint32(env, val, &conn);
109
+ opts.connectivity = conn;
110
+ }
111
+ }
112
+
113
+ // multi - check type
114
+ if (js_get_named_property(env, argv[0], "multi", &val) == 0) {
115
+ js_typeof(env, val, &val_type);
116
+ if (val_type == js_boolean) {
117
+ bool multi;
118
+ js_get_value_bool(env, val, &multi);
119
+ opts.multi = multi;
120
+ }
121
+ }
122
+ }
123
+ usearch_error_t error = NULL;
124
+ usearch_index_t index = usearch_init_safe(&opts, &error);
125
+ if (error) {
126
+ throw_if_error(env, error);
127
+ js_value_t *undef;
128
+ js_get_undefined(env, &undef);
129
+ return undef;
130
+ }
131
+
132
+ // Reserve initial capacity (required before adding vectors)
133
+ usearch_reserve_safe(index, 1024, &error);
134
+ if (error) {
135
+ usearch_free_safe(index, &error);
136
+ throw_if_error(env, "Failed to reserve initial capacity");
137
+ js_value_t *undef;
138
+ js_get_undefined(env, &undef);
139
+ return undef;
140
+ }
141
+
142
+ js_value_t *result;
143
+ js_create_external(env, index, NULL, NULL, &result);
144
+ return result;
145
+ }
146
+
147
+ // Free an index
148
+ static js_value_t *fn_free(js_env_t *env, js_callback_info_t *info) {
149
+ size_t argc = 1;
150
+ js_value_t *argv[1];
151
+ js_get_callback_info(env, info, &argc, argv, NULL, NULL);
152
+
153
+ usearch_index_t index;
154
+ js_get_value_external(env, argv[0], (void **)&index);
155
+
156
+ usearch_error_t error = NULL;
157
+ usearch_free_safe(index, &error);
158
+ throw_if_error(env, error);
159
+
160
+ js_value_t *undef;
161
+ js_get_undefined(env, &undef);
162
+ return undef;
163
+ }
164
+
165
+ // Get index size
166
+ static js_value_t *fn_size(js_env_t *env, js_callback_info_t *info) {
167
+ size_t argc = 1;
168
+ js_value_t *argv[1];
169
+ js_get_callback_info(env, info, &argc, argv, NULL, NULL);
170
+
171
+ usearch_index_t index;
172
+ js_get_value_external(env, argv[0], (void **)&index);
173
+
174
+ usearch_error_t error = NULL;
175
+ size_t size = usearch_size_safe(index, &error);
176
+ throw_if_error(env, error);
177
+
178
+ js_value_t *result;
179
+ js_create_uint32(env, (uint32_t)size, &result);
180
+ return result;
181
+ }
182
+
183
+ // Get index dimensions
184
+ static js_value_t *fn_dimensions(js_env_t *env, js_callback_info_t *info) {
185
+ size_t argc = 1;
186
+ js_value_t *argv[1];
187
+ js_get_callback_info(env, info, &argc, argv, NULL, NULL);
188
+
189
+ usearch_index_t index;
190
+ js_get_value_external(env, argv[0], (void **)&index);
191
+
192
+ usearch_error_t error = NULL;
193
+ size_t dims = usearch_dimensions_safe(index, &error);
194
+ throw_if_error(env, error);
195
+
196
+ js_value_t *result;
197
+ js_create_uint32(env, (uint32_t)dims, &result);
198
+ return result;
199
+ }
200
+
201
+ // Get index capacity
202
+ static js_value_t *fn_capacity(js_env_t *env, js_callback_info_t *info) {
203
+ size_t argc = 1;
204
+ js_value_t *argv[1];
205
+ js_get_callback_info(env, info, &argc, argv, NULL, NULL);
206
+
207
+ usearch_index_t index;
208
+ js_get_value_external(env, argv[0], (void **)&index);
209
+
210
+ usearch_error_t error = NULL;
211
+ size_t cap = usearch_capacity_safe(index, &error);
212
+ throw_if_error(env, error);
213
+
214
+ js_value_t *result;
215
+ js_create_uint32(env, (uint32_t)cap, &result);
216
+ return result;
217
+ }
218
+
219
+ // Reserve capacity
220
+ static js_value_t *fn_reserve(js_env_t *env, js_callback_info_t *info) {
221
+ size_t argc = 2;
222
+ js_value_t *argv[2];
223
+ js_get_callback_info(env, info, &argc, argv, NULL, NULL);
224
+
225
+ usearch_index_t index;
226
+ js_get_value_external(env, argv[0], (void **)&index);
227
+
228
+ uint32_t capacity;
229
+ js_get_value_uint32(env, argv[1], &capacity);
230
+
231
+ usearch_error_t error = NULL;
232
+ usearch_reserve_safe(index, capacity, &error);
233
+ throw_if_error(env, error);
234
+
235
+ js_value_t *undef;
236
+ js_get_undefined(env, &undef);
237
+ return undef;
238
+ }
239
+
240
+ // Add a vector
241
+ static js_value_t *fn_add(js_env_t *env, js_callback_info_t *info) {
242
+ size_t argc = 3;
243
+ js_value_t *argv[3];
244
+ js_get_callback_info(env, info, &argc, argv, NULL, NULL);
245
+
246
+ usearch_index_t index;
247
+ js_get_value_external(env, argv[0], (void **)&index);
248
+
249
+ int64_t key;
250
+ js_get_value_int64(env, argv[1], &key);
251
+
252
+ // Get Float32Array data
253
+ void *data;
254
+ size_t length;
255
+ js_typedarray_type_t type;
256
+ js_get_typedarray_info(env, argv[2], &type, &data, &length, NULL, NULL);
257
+
258
+ usearch_error_t error = NULL;
259
+ usearch_add_safe(index, (usearch_key_t)key, data, usearch_scalar_f32_k, &error);
260
+ throw_if_error(env, error);
261
+
262
+ js_value_t *undef;
263
+ js_get_undefined(env, &undef);
264
+ return undef;
265
+ }
266
+
267
+ // Search for nearest neighbors
268
+ static js_value_t *fn_search(js_env_t *env, js_callback_info_t *info) {
269
+ size_t argc = 3;
270
+ js_value_t *argv[3];
271
+ js_get_callback_info(env, info, &argc, argv, NULL, NULL);
272
+
273
+ usearch_index_t index;
274
+ js_get_value_external(env, argv[0], (void **)&index);
275
+
276
+ // Query vector
277
+ void *query_data;
278
+ size_t query_length;
279
+ js_typedarray_type_t type;
280
+ js_get_typedarray_info(env, argv[1], &type, &query_data, &query_length, NULL, NULL);
281
+
282
+ // Count (k)
283
+ uint32_t count;
284
+ js_get_value_uint32(env, argv[2], &count);
285
+
286
+ // Allocate output buffers
287
+ usearch_key_t *keys = (usearch_key_t *)malloc(count * sizeof(usearch_key_t));
288
+ usearch_distance_t *distances = (usearch_distance_t *)malloc(count * sizeof(usearch_distance_t));
289
+
290
+ usearch_error_t error = NULL;
291
+ size_t found = usearch_search_safe(index, query_data, usearch_scalar_f32_k, count, keys, distances, &error);
292
+
293
+ if (error) {
294
+ free(keys);
295
+ free(distances);
296
+ throw_if_error(env, error);
297
+ js_value_t *undef;
298
+ js_get_undefined(env, &undef);
299
+ return undef;
300
+ }
301
+
302
+ // Create result object { keys: BigUint64Array, distances: Float32Array, count: number }
303
+ js_value_t *result;
304
+ js_create_object(env, &result);
305
+
306
+ // Keys array (BigUint64Array)
307
+ js_value_t *keys_ab, *keys_arr;
308
+ void *keys_buf;
309
+ js_create_arraybuffer(env, found * sizeof(uint64_t), &keys_buf, &keys_ab);
310
+ memcpy(keys_buf, keys, found * sizeof(uint64_t));
311
+ js_create_typedarray(env, js_biguint64array, found, keys_ab, 0, &keys_arr);
312
+ js_set_named_property(env, result, "keys", keys_arr);
313
+
314
+ // Distances array (Float32Array)
315
+ js_value_t *dist_ab, *dist_arr;
316
+ void *dist_buf;
317
+ js_create_arraybuffer(env, found * sizeof(float), &dist_buf, &dist_ab);
318
+ memcpy(dist_buf, distances, found * sizeof(float));
319
+ js_create_typedarray(env, js_float32array, found, dist_ab, 0, &dist_arr);
320
+ js_set_named_property(env, result, "distances", dist_arr);
321
+
322
+ // Count
323
+ js_value_t *count_val;
324
+ js_create_uint32(env, (uint32_t)found, &count_val);
325
+ js_set_named_property(env, result, "count", count_val);
326
+
327
+ free(keys);
328
+ free(distances);
329
+
330
+ return result;
331
+ }
332
+
333
+ // Remove a vector
334
+ static js_value_t *fn_remove(js_env_t *env, js_callback_info_t *info) {
335
+ size_t argc = 2;
336
+ js_value_t *argv[2];
337
+ js_get_callback_info(env, info, &argc, argv, NULL, NULL);
338
+
339
+ usearch_index_t index;
340
+ js_get_value_external(env, argv[0], (void **)&index);
341
+
342
+ int64_t key;
343
+ js_get_value_int64(env, argv[1], &key);
344
+
345
+ usearch_error_t error = NULL;
346
+ size_t removed = usearch_remove_safe(index, (usearch_key_t)key, &error);
347
+ throw_if_error(env, error);
348
+
349
+ js_value_t *result;
350
+ js_create_uint32(env, (uint32_t)removed, &result);
351
+ return result;
352
+ }
353
+
354
+ // Check if index contains a key
355
+ static js_value_t *fn_contains(js_env_t *env, js_callback_info_t *info) {
356
+ size_t argc = 2;
357
+ js_value_t *argv[2];
358
+ js_get_callback_info(env, info, &argc, argv, NULL, NULL);
359
+
360
+ usearch_index_t index;
361
+ js_get_value_external(env, argv[0], (void **)&index);
362
+
363
+ int64_t key;
364
+ js_get_value_int64(env, argv[1], &key);
365
+
366
+ usearch_error_t error = NULL;
367
+ bool exists = usearch_contains_safe(index, (usearch_key_t)key, &error);
368
+ throw_if_error(env, error);
369
+
370
+ js_value_t *result;
371
+ js_get_boolean(env, exists, &result);
372
+ return result;
373
+ }
374
+
375
+ // Save index to file
376
+ static js_value_t *fn_save(js_env_t *env, js_callback_info_t *info) {
377
+ size_t argc = 2;
378
+ js_value_t *argv[2];
379
+ js_get_callback_info(env, info, &argc, argv, NULL, NULL);
380
+
381
+ usearch_index_t index;
382
+ js_get_value_external(env, argv[0], (void **)&index);
383
+
384
+ utf8_t path[4096];
385
+ size_t path_len;
386
+ js_get_value_string_utf8(env, argv[1], path, sizeof(path), &path_len);
387
+
388
+ usearch_error_t error = NULL;
389
+ usearch_save_safe(index, (char *)path, &error);
390
+ throw_if_error(env, error);
391
+
392
+ js_value_t *undef;
393
+ js_get_undefined(env, &undef);
394
+ return undef;
395
+ }
396
+
397
+ // Load index from file
398
+ static js_value_t *fn_load(js_env_t *env, js_callback_info_t *info) {
399
+ size_t argc = 2;
400
+ js_value_t *argv[2];
401
+ js_get_callback_info(env, info, &argc, argv, NULL, NULL);
402
+
403
+ usearch_index_t index;
404
+ js_get_value_external(env, argv[0], (void **)&index);
405
+
406
+ utf8_t path[4096];
407
+ size_t path_len;
408
+ js_get_value_string_utf8(env, argv[1], path, sizeof(path), &path_len);
409
+
410
+ usearch_error_t error = NULL;
411
+ usearch_load_safe(index, (char *)path, &error);
412
+ throw_if_error(env, error);
413
+
414
+ js_value_t *undef;
415
+ js_get_undefined(env, &undef);
416
+ return undef;
417
+ }
418
+
419
+ // View index from file (memory-mapped)
420
+ static js_value_t *fn_view(js_env_t *env, js_callback_info_t *info) {
421
+ size_t argc = 2;
422
+ js_value_t *argv[2];
423
+ js_get_callback_info(env, info, &argc, argv, NULL, NULL);
424
+
425
+ usearch_index_t index;
426
+ js_get_value_external(env, argv[0], (void **)&index);
427
+
428
+ utf8_t path[4096];
429
+ size_t path_len;
430
+ js_get_value_string_utf8(env, argv[1], path, sizeof(path), &path_len);
431
+
432
+ usearch_error_t error = NULL;
433
+ usearch_view_safe(index, (char *)path, &error);
434
+ throw_if_error(env, error);
435
+
436
+ js_value_t *undef;
437
+ js_get_undefined(env, &undef);
438
+ return undef;
439
+ }
440
+
441
+ // Get vector by key
442
+ static js_value_t *fn_get(js_env_t *env, js_callback_info_t *info) {
443
+ size_t argc = 2;
444
+ js_value_t *argv[2];
445
+ js_get_callback_info(env, info, &argc, argv, NULL, NULL);
446
+
447
+ usearch_index_t index;
448
+ js_get_value_external(env, argv[0], (void **)&index);
449
+
450
+ int64_t key;
451
+ js_get_value_int64(env, argv[1], &key);
452
+
453
+ // Get dimensions to allocate buffer
454
+ usearch_error_t error = NULL;
455
+ size_t dims = usearch_dimensions_safe(index, &error);
456
+ if (error) {
457
+ throw_if_error(env, error);
458
+ js_value_t *undef;
459
+ js_get_undefined(env, &undef);
460
+ return undef;
461
+ }
462
+
463
+ // Create output Float32Array
464
+ js_value_t *ab, *arr;
465
+ void *data;
466
+ js_create_arraybuffer(env, dims * sizeof(float), &data, &ab);
467
+
468
+ error = NULL;
469
+ size_t found = usearch_get_safe(index, (usearch_key_t)key, 1, data, usearch_scalar_f32_k, &error);
470
+
471
+ if (error || found == 0) {
472
+ if (error) throw_if_error(env, error);
473
+ js_value_t *null_val;
474
+ js_get_null(env, &null_val);
475
+ return null_val;
476
+ }
477
+
478
+ js_create_typedarray(env, js_float32array, dims, ab, 0, &arr);
479
+ return arr;
480
+ }
481
+
482
+ // Clear index
483
+ static js_value_t *fn_clear(js_env_t *env, js_callback_info_t *info) {
484
+ size_t argc = 1;
485
+ js_value_t *argv[1];
486
+ js_get_callback_info(env, info, &argc, argv, NULL, NULL);
487
+
488
+ usearch_index_t index;
489
+ js_get_value_external(env, argv[0], (void **)&index);
490
+
491
+ usearch_error_t error = NULL;
492
+ usearch_clear_safe(index, &error);
493
+ throw_if_error(env, error);
494
+
495
+ js_value_t *undef;
496
+ js_get_undefined(env, &undef);
497
+ return undef;
498
+ }
499
+
500
+ static js_value_t *init(js_env_t *env, js_value_t *exports) {
501
+ // Define all exports
502
+ #define EXPORT_FN(name, fn) { \
503
+ js_value_t *val; \
504
+ js_create_function(env, name, -1, fn, NULL, &val); \
505
+ js_set_named_property(env, exports, name, val); \
506
+ }
507
+
508
+ EXPORT_FN("init", fn_init)
509
+ EXPORT_FN("free", fn_free)
510
+ EXPORT_FN("size", fn_size)
511
+ EXPORT_FN("dimensions", fn_dimensions)
512
+ EXPORT_FN("capacity", fn_capacity)
513
+ EXPORT_FN("reserve", fn_reserve)
514
+ EXPORT_FN("add", fn_add)
515
+ EXPORT_FN("search", fn_search)
516
+ EXPORT_FN("remove", fn_remove)
517
+ EXPORT_FN("contains", fn_contains)
518
+ EXPORT_FN("get", fn_get)
519
+ EXPORT_FN("save", fn_save)
520
+ EXPORT_FN("load", fn_load)
521
+ EXPORT_FN("view", fn_view)
522
+ EXPORT_FN("clear", fn_clear)
523
+
524
+ #undef EXPORT_FN
525
+
526
+ return exports;
527
+ }
528
+
529
+ BARE_MODULE(usearch, init)
package/binding.js ADDED
@@ -0,0 +1,2 @@
1
+ require.addon = require('require-addon')
2
+ module.exports = require.addon('.', __filename)
package/index.js ADDED
@@ -0,0 +1,84 @@
1
+ const binding = require('./binding')
2
+
3
+ class Index {
4
+ constructor (opts = {}) {
5
+ this._handle = binding.init(opts)
6
+ }
7
+
8
+ get size () {
9
+ return binding.size(this._handle)
10
+ }
11
+
12
+ get dimensions () {
13
+ return binding.dimensions(this._handle)
14
+ }
15
+
16
+ get capacity () {
17
+ return binding.capacity(this._handle)
18
+ }
19
+
20
+ reserve (capacity) {
21
+ binding.reserve(this._handle, capacity)
22
+ }
23
+
24
+ add (key, vector) {
25
+ if (!(vector instanceof Float32Array)) {
26
+ vector = new Float32Array(vector)
27
+ }
28
+ binding.add(this._handle, key, vector)
29
+ }
30
+
31
+ search (query, k = 10) {
32
+ if (!(query instanceof Float32Array)) {
33
+ query = new Float32Array(query)
34
+ }
35
+ const result = binding.search(this._handle, query, k)
36
+ // Convert BigUint64Array keys to regular numbers for convenience
37
+ const keys = new Array(result.count)
38
+ for (let i = 0; i < result.count; i++) {
39
+ keys[i] = Number(result.keys[i])
40
+ }
41
+ return {
42
+ keys,
43
+ distances: result.distances,
44
+ count: result.count
45
+ }
46
+ }
47
+
48
+ remove (key) {
49
+ return binding.remove(this._handle, key)
50
+ }
51
+
52
+ contains (key) {
53
+ return binding.contains(this._handle, key)
54
+ }
55
+
56
+ get (key) {
57
+ return binding.get(this._handle, key)
58
+ }
59
+
60
+ save (path) {
61
+ binding.save(this._handle, path)
62
+ }
63
+
64
+ load (path) {
65
+ binding.load(this._handle, path)
66
+ }
67
+
68
+ view (path) {
69
+ binding.view(this._handle, path)
70
+ }
71
+
72
+ clear () {
73
+ binding.clear(this._handle)
74
+ }
75
+
76
+ free () {
77
+ if (this._handle) {
78
+ binding.free(this._handle)
79
+ this._handle = null
80
+ }
81
+ }
82
+ }
83
+
84
+ module.exports = { Index, binding }
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "bare-usearch",
3
+ "version": "0.1.0",
4
+ "description": "USearch vector similarity search bindings for Bare",
5
+ "main": "index.js",
6
+ "addon": true,
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/CameronTofer/bare-usearch.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/CameronTofer/bare-usearch/issues"
14
+ },
15
+ "homepage": "https://github.com/CameronTofer/bare-usearch",
16
+ "keywords": [
17
+ "usearch",
18
+ "vector",
19
+ "similarity",
20
+ "search",
21
+ "hnsw",
22
+ "bare",
23
+ "native"
24
+ ],
25
+ "imports": {
26
+ "fs": {
27
+ "bare": "bare-fs",
28
+ "default": "fs"
29
+ },
30
+ "fs/*": {
31
+ "bare": "bare-fs/*",
32
+ "default": "fs/*"
33
+ },
34
+ "path": {
35
+ "bare": "bare-path",
36
+ "default": "path"
37
+ },
38
+ "os": {
39
+ "bare": "bare-os",
40
+ "default": "os"
41
+ }
42
+ },
43
+ "engines": {
44
+ "bare": ">=1.16.0"
45
+ },
46
+ "files": [
47
+ "index.js",
48
+ "binding.js",
49
+ "binding.c",
50
+ "usearch_wrapper.cpp",
51
+ "prebuilds",
52
+ "CMakeLists.txt",
53
+ "vendor/usearch/c",
54
+ "vendor/usearch/include",
55
+ "vendor/usearch/fp16/include",
56
+ "vendor/usearch/VERSION"
57
+ ],
58
+ "scripts": {
59
+ "build": "bare-make generate && bare-make build && bare-make install",
60
+ "test": "npm run test:bare",
61
+ "test:bare": "brittle-bare test/*.js",
62
+ "test:node": "brittle test/*.js",
63
+ "clean": "rm -rf build prebuilds",
64
+ "update-usearch": "git submodule update --remote --recursive vendor/usearch"
65
+ },
66
+ "dependencies": {
67
+ "require-addon": "^1.0.0"
68
+ },
69
+ "devDependencies": {
70
+ "bare-compat-napi": "^1.3.5",
71
+ "bare-fs": "^4.5.3",
72
+ "bare-os": "^3.6.2",
73
+ "bare-path": "^3.0.0",
74
+ "brittle": "^3.19.1",
75
+ "cmake-bare": "^1.1.2",
76
+ "cmake-napi": "^1.2.1"
77
+ }
78
+ }
@@ -0,0 +1,164 @@
1
+ // Wrapper that catches C++ exceptions and converts to C error strings
2
+ // This prevents exceptions from escaping into the Bare runtime (compiled with -fno-exceptions)
3
+
4
+ #include <cstring>
5
+ #include <cstdlib>
6
+ #include <exception>
7
+
8
+ extern "C" {
9
+ #include "usearch.h"
10
+ }
11
+
12
+ // Thread-local buffer for error messages
13
+ static thread_local char error_buffer[256];
14
+
15
+ static const char* capture_exception() {
16
+ try {
17
+ throw; // rethrow current exception
18
+ } catch (const std::exception& e) {
19
+ std::strncpy(error_buffer, e.what(), sizeof(error_buffer) - 1);
20
+ error_buffer[sizeof(error_buffer) - 1] = '\0';
21
+ return error_buffer;
22
+ } catch (...) {
23
+ return "Unknown C++ exception";
24
+ }
25
+ }
26
+
27
+ // Wrapped versions of usearch functions that catch exceptions
28
+
29
+ extern "C" {
30
+
31
+ USEARCH_EXPORT usearch_index_t usearch_init_safe(usearch_init_options_t* options, usearch_error_t* error) {
32
+ try {
33
+ return usearch_init(options, error);
34
+ } catch (...) {
35
+ *error = capture_exception();
36
+ return nullptr;
37
+ }
38
+ }
39
+
40
+ USEARCH_EXPORT void usearch_free_safe(usearch_index_t index, usearch_error_t* error) {
41
+ try {
42
+ usearch_free(index, error);
43
+ } catch (...) {
44
+ *error = capture_exception();
45
+ }
46
+ }
47
+
48
+ USEARCH_EXPORT size_t usearch_size_safe(usearch_index_t index, usearch_error_t* error) {
49
+ try {
50
+ return usearch_size(index, error);
51
+ } catch (...) {
52
+ *error = capture_exception();
53
+ return 0;
54
+ }
55
+ }
56
+
57
+ USEARCH_EXPORT size_t usearch_capacity_safe(usearch_index_t index, usearch_error_t* error) {
58
+ try {
59
+ return usearch_capacity(index, error);
60
+ } catch (...) {
61
+ *error = capture_exception();
62
+ return 0;
63
+ }
64
+ }
65
+
66
+ USEARCH_EXPORT size_t usearch_dimensions_safe(usearch_index_t index, usearch_error_t* error) {
67
+ try {
68
+ return usearch_dimensions(index, error);
69
+ } catch (...) {
70
+ *error = capture_exception();
71
+ return 0;
72
+ }
73
+ }
74
+
75
+ USEARCH_EXPORT void usearch_reserve_safe(usearch_index_t index, size_t capacity, usearch_error_t* error) {
76
+ try {
77
+ usearch_reserve(index, capacity, error);
78
+ } catch (...) {
79
+ *error = capture_exception();
80
+ }
81
+ }
82
+
83
+ USEARCH_EXPORT void usearch_add_safe(usearch_index_t index, usearch_key_t key, void const* vector,
84
+ usearch_scalar_kind_t kind, usearch_error_t* error) {
85
+ try {
86
+ usearch_add(index, key, vector, kind, error);
87
+ } catch (...) {
88
+ *error = capture_exception();
89
+ }
90
+ }
91
+
92
+ USEARCH_EXPORT size_t usearch_search_safe(usearch_index_t index, void const* query, usearch_scalar_kind_t kind,
93
+ size_t count, usearch_key_t* keys, usearch_distance_t* distances,
94
+ usearch_error_t* error) {
95
+ try {
96
+ return usearch_search(index, query, kind, count, keys, distances, error);
97
+ } catch (...) {
98
+ *error = capture_exception();
99
+ return 0;
100
+ }
101
+ }
102
+
103
+ USEARCH_EXPORT size_t usearch_remove_safe(usearch_index_t index, usearch_key_t key, usearch_error_t* error) {
104
+ try {
105
+ return usearch_remove(index, key, error);
106
+ } catch (...) {
107
+ *error = capture_exception();
108
+ return 0;
109
+ }
110
+ }
111
+
112
+ USEARCH_EXPORT bool usearch_contains_safe(usearch_index_t index, usearch_key_t key, usearch_error_t* error) {
113
+ try {
114
+ return usearch_contains(index, key, error);
115
+ } catch (...) {
116
+ *error = capture_exception();
117
+ return false;
118
+ }
119
+ }
120
+
121
+ USEARCH_EXPORT size_t usearch_get_safe(usearch_index_t index, usearch_key_t key, size_t count,
122
+ void* vector, usearch_scalar_kind_t kind, usearch_error_t* error) {
123
+ try {
124
+ return usearch_get(index, key, count, vector, kind, error);
125
+ } catch (...) {
126
+ *error = capture_exception();
127
+ return 0;
128
+ }
129
+ }
130
+
131
+ USEARCH_EXPORT void usearch_save_safe(usearch_index_t index, char const* path, usearch_error_t* error) {
132
+ try {
133
+ usearch_save(index, path, error);
134
+ } catch (...) {
135
+ *error = capture_exception();
136
+ }
137
+ }
138
+
139
+ USEARCH_EXPORT void usearch_load_safe(usearch_index_t index, char const* path, usearch_error_t* error) {
140
+ try {
141
+ usearch_load(index, path, error);
142
+ } catch (...) {
143
+ *error = capture_exception();
144
+ }
145
+ }
146
+
147
+ USEARCH_EXPORT void usearch_view_safe(usearch_index_t index, char const* path, usearch_error_t* error) {
148
+ try {
149
+ usearch_view(index, path, error);
150
+ } catch (...) {
151
+ *error = capture_exception();
152
+ }
153
+ }
154
+
155
+ USEARCH_EXPORT void usearch_clear_safe(usearch_index_t index, usearch_error_t* error) {
156
+ try {
157
+ usearch_clear(index, error);
158
+ } catch (...) {
159
+ *error = capture_exception();
160
+ }
161
+ }
162
+
163
+ } // extern "C"
164
+