@faiss-node/native 0.1.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.
- package/.env.example +5 -0
- package/0.1.1 +0 -0
- package/API.md +416 -0
- package/DOCUMENTATION.md +203 -0
- package/LICENSE +21 -0
- package/QUICK_PUBLISH.md +48 -0
- package/README.md +551 -0
- package/binding.gyp +90 -0
- package/docs/README.md +31 -0
- package/package.json +69 -0
- package/src/cpp/faiss_index.cpp +300 -0
- package/src/cpp/faiss_index.h +99 -0
- package/src/cpp/napi_bindings.cpp +969 -0
- package/src/js/index.js +349 -0
- package/src/js/types.d.ts +36 -0
- package/tsconfig.json +25 -0
- package/typedoc.json +30 -0
|
@@ -0,0 +1,969 @@
|
|
|
1
|
+
#include <napi.h>
|
|
2
|
+
// Include FAISS headers for idx_t
|
|
3
|
+
#include <faiss/MetricType.h>
|
|
4
|
+
#include "faiss_index.h"
|
|
5
|
+
#include <vector>
|
|
6
|
+
#include <memory>
|
|
7
|
+
#include <cstring>
|
|
8
|
+
#include <string>
|
|
9
|
+
|
|
10
|
+
// Forward declaration
|
|
11
|
+
class FaissIndexWrapperJS;
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Async Workers for Non-Blocking Operations
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
// Add Worker
|
|
18
|
+
class AddWorker : public Napi::AsyncWorker {
|
|
19
|
+
public:
|
|
20
|
+
AddWorker(FaissIndexWrapper* wrapper, const float* vectors, size_t n, int dims, Napi::Promise::Deferred deferred)
|
|
21
|
+
: Napi::AsyncWorker(deferred.Env(), "AddWorker"),
|
|
22
|
+
wrapper_(wrapper),
|
|
23
|
+
vectors_(vectors, vectors + n * dims),
|
|
24
|
+
n_(n),
|
|
25
|
+
deferred_(deferred) {
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
void Execute() override {
|
|
29
|
+
try {
|
|
30
|
+
if (wrapper_->IsDisposed()) {
|
|
31
|
+
SetError("Index has been disposed");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
wrapper_->Add(vectors_.data(), n_);
|
|
35
|
+
} catch (const std::exception& e) {
|
|
36
|
+
SetError(std::string("FAISS error: ") + e.what());
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
void OnOK() override {
|
|
41
|
+
deferred_.Resolve(Env().Undefined());
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
void OnError(const Napi::Error& e) override {
|
|
45
|
+
deferred_.Reject(e.Value());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private:
|
|
49
|
+
FaissIndexWrapper* wrapper_;
|
|
50
|
+
std::vector<float> vectors_;
|
|
51
|
+
size_t n_;
|
|
52
|
+
Napi::Promise::Deferred deferred_;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Train Worker
|
|
56
|
+
class TrainWorker : public Napi::AsyncWorker {
|
|
57
|
+
public:
|
|
58
|
+
TrainWorker(FaissIndexWrapper* wrapper, const float* vectors, size_t n, int dims, Napi::Promise::Deferred deferred)
|
|
59
|
+
: Napi::AsyncWorker(deferred.Env(), "TrainWorker"),
|
|
60
|
+
wrapper_(wrapper),
|
|
61
|
+
vectors_(vectors, vectors + n * dims),
|
|
62
|
+
n_(n),
|
|
63
|
+
deferred_(deferred) {
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
void Execute() override {
|
|
67
|
+
try {
|
|
68
|
+
if (wrapper_->IsDisposed()) {
|
|
69
|
+
SetError("Index has been disposed");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
wrapper_->Train(vectors_.data(), n_);
|
|
73
|
+
} catch (const std::exception& e) {
|
|
74
|
+
SetError(std::string("FAISS error: ") + e.what());
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
void OnOK() override {
|
|
79
|
+
deferred_.Resolve(Env().Undefined());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
void OnError(const Napi::Error& e) override {
|
|
83
|
+
deferred_.Reject(e.Value());
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private:
|
|
87
|
+
FaissIndexWrapper* wrapper_;
|
|
88
|
+
std::vector<float> vectors_;
|
|
89
|
+
size_t n_;
|
|
90
|
+
Napi::Promise::Deferred deferred_;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Search Worker
|
|
94
|
+
class SearchWorker : public Napi::AsyncWorker {
|
|
95
|
+
public:
|
|
96
|
+
SearchWorker(FaissIndexWrapper* wrapper, const float* query, int k, Napi::Promise::Deferred deferred)
|
|
97
|
+
: Napi::AsyncWorker(deferred.Env(), "SearchWorker"),
|
|
98
|
+
wrapper_(wrapper),
|
|
99
|
+
query_(query, query + wrapper->GetDimensions()),
|
|
100
|
+
k_(k),
|
|
101
|
+
deferred_(deferred) {
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
void Execute() override {
|
|
105
|
+
try {
|
|
106
|
+
if (wrapper_->IsDisposed()) {
|
|
107
|
+
SetError("Index has been disposed");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
size_t ntotal = wrapper_->GetTotalVectors();
|
|
112
|
+
if (ntotal == 0) {
|
|
113
|
+
SetError("Cannot search empty index");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
int actual_k = (k_ > static_cast<int>(ntotal)) ? static_cast<int>(ntotal) : k_;
|
|
118
|
+
distances_.resize(actual_k);
|
|
119
|
+
labels_.resize(actual_k);
|
|
120
|
+
|
|
121
|
+
wrapper_->Search(query_.data(), actual_k, distances_.data(), labels_.data());
|
|
122
|
+
} catch (const std::exception& e) {
|
|
123
|
+
SetError(std::string("FAISS error: ") + e.what());
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
void OnOK() override {
|
|
128
|
+
Napi::Env env = Env();
|
|
129
|
+
Napi::Object result = Napi::Object::New(env);
|
|
130
|
+
|
|
131
|
+
Napi::Float32Array distances = Napi::Float32Array::New(env, distances_.size());
|
|
132
|
+
memcpy(distances.Data(), distances_.data(), distances_.size() * sizeof(float));
|
|
133
|
+
|
|
134
|
+
Napi::Int32Array labels = Napi::Int32Array::New(env, labels_.size());
|
|
135
|
+
int32_t* labelsData = labels.Data();
|
|
136
|
+
for (size_t i = 0; i < labels_.size(); i++) {
|
|
137
|
+
labelsData[i] = static_cast<int32_t>(labels_[i]);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
result.Set("distances", distances);
|
|
141
|
+
result.Set("labels", labels);
|
|
142
|
+
deferred_.Resolve(result);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
void OnError(const Napi::Error& e) override {
|
|
146
|
+
deferred_.Reject(e.Value());
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private:
|
|
150
|
+
FaissIndexWrapper* wrapper_;
|
|
151
|
+
std::vector<float> query_;
|
|
152
|
+
int k_;
|
|
153
|
+
std::vector<float> distances_;
|
|
154
|
+
std::vector<faiss::idx_t> labels_;
|
|
155
|
+
Napi::Promise::Deferred deferred_;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// SearchBatch Worker
|
|
159
|
+
class SearchBatchWorker : public Napi::AsyncWorker {
|
|
160
|
+
public:
|
|
161
|
+
SearchBatchWorker(FaissIndexWrapper* wrapper, const float* queries, size_t nq, int k, Napi::Promise::Deferred deferred)
|
|
162
|
+
: Napi::AsyncWorker(deferred.Env(), "SearchBatchWorker"),
|
|
163
|
+
wrapper_(wrapper),
|
|
164
|
+
queries_(queries, queries + nq * wrapper->GetDimensions()),
|
|
165
|
+
nq_(nq),
|
|
166
|
+
k_(k),
|
|
167
|
+
deferred_(deferred) {
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
void Execute() override {
|
|
171
|
+
try {
|
|
172
|
+
if (wrapper_->IsDisposed()) {
|
|
173
|
+
SetError("Index has been disposed");
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
size_t ntotal = wrapper_->GetTotalVectors();
|
|
178
|
+
if (ntotal == 0) {
|
|
179
|
+
SetError("Cannot search empty index");
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
int actual_k = (k_ > static_cast<int>(ntotal)) ? static_cast<int>(ntotal) : k_;
|
|
184
|
+
distances_.resize(nq_ * actual_k);
|
|
185
|
+
labels_.resize(nq_ * actual_k);
|
|
186
|
+
|
|
187
|
+
wrapper_->SearchBatch(queries_.data(), nq_, actual_k, distances_.data(), labels_.data());
|
|
188
|
+
} catch (const std::exception& e) {
|
|
189
|
+
SetError(std::string("FAISS error: ") + e.what());
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
void OnOK() override {
|
|
194
|
+
Napi::Env env = Env();
|
|
195
|
+
Napi::Object result = Napi::Object::New(env);
|
|
196
|
+
|
|
197
|
+
Napi::Float32Array distances = Napi::Float32Array::New(env, distances_.size());
|
|
198
|
+
memcpy(distances.Data(), distances_.data(), distances_.size() * sizeof(float));
|
|
199
|
+
|
|
200
|
+
Napi::Int32Array labels = Napi::Int32Array::New(env, labels_.size());
|
|
201
|
+
int32_t* labelsData = labels.Data();
|
|
202
|
+
for (size_t i = 0; i < labels_.size(); i++) {
|
|
203
|
+
labelsData[i] = static_cast<int32_t>(labels_[i]);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
result.Set("distances", distances);
|
|
207
|
+
result.Set("labels", labels);
|
|
208
|
+
result.Set("nq", Napi::Number::New(env, nq_));
|
|
209
|
+
result.Set("k", Napi::Number::New(env, static_cast<int>(distances_.size() / nq_)));
|
|
210
|
+
deferred_.Resolve(result);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
void OnError(const Napi::Error& e) override {
|
|
214
|
+
deferred_.Reject(e.Value());
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private:
|
|
218
|
+
FaissIndexWrapper* wrapper_;
|
|
219
|
+
std::vector<float> queries_;
|
|
220
|
+
size_t nq_;
|
|
221
|
+
int k_;
|
|
222
|
+
std::vector<float> distances_;
|
|
223
|
+
std::vector<faiss::idx_t> labels_;
|
|
224
|
+
Napi::Promise::Deferred deferred_;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// Save Worker
|
|
228
|
+
class SaveWorker : public Napi::AsyncWorker {
|
|
229
|
+
public:
|
|
230
|
+
SaveWorker(FaissIndexWrapper* wrapper, const std::string& filename, Napi::Promise::Deferred deferred)
|
|
231
|
+
: Napi::AsyncWorker(deferred.Env(), "SaveWorker"),
|
|
232
|
+
wrapper_(wrapper),
|
|
233
|
+
filename_(filename),
|
|
234
|
+
deferred_(deferred) {
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
void Execute() override {
|
|
238
|
+
try {
|
|
239
|
+
if (wrapper_->IsDisposed()) {
|
|
240
|
+
SetError("Index has been disposed");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
wrapper_->Save(filename_);
|
|
244
|
+
} catch (const std::exception& e) {
|
|
245
|
+
SetError(std::string("FAISS error: ") + e.what());
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
void OnOK() override {
|
|
250
|
+
deferred_.Resolve(Env().Undefined());
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
void OnError(const Napi::Error& e) override {
|
|
254
|
+
deferred_.Reject(e.Value());
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private:
|
|
258
|
+
FaissIndexWrapper* wrapper_;
|
|
259
|
+
std::string filename_;
|
|
260
|
+
Napi::Promise::Deferred deferred_;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// ToBuffer Worker
|
|
264
|
+
class ToBufferWorker : public Napi::AsyncWorker {
|
|
265
|
+
public:
|
|
266
|
+
ToBufferWorker(FaissIndexWrapper* wrapper, Napi::Promise::Deferred deferred)
|
|
267
|
+
: Napi::AsyncWorker(deferred.Env(), "ToBufferWorker"),
|
|
268
|
+
wrapper_(wrapper),
|
|
269
|
+
deferred_(deferred) {
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
void Execute() override {
|
|
273
|
+
try {
|
|
274
|
+
if (wrapper_->IsDisposed()) {
|
|
275
|
+
SetError("Index has been disposed");
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
buffer_ = wrapper_->ToBuffer();
|
|
279
|
+
} catch (const std::exception& e) {
|
|
280
|
+
SetError(std::string("FAISS error: ") + e.what());
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
void OnOK() override {
|
|
285
|
+
Napi::Env env = Env();
|
|
286
|
+
Napi::Buffer<uint8_t> nodeBuffer = Napi::Buffer<uint8_t>::Copy(env, buffer_.data(), buffer_.size());
|
|
287
|
+
deferred_.Resolve(nodeBuffer);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
void OnError(const Napi::Error& e) override {
|
|
291
|
+
deferred_.Reject(e.Value());
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private:
|
|
295
|
+
FaissIndexWrapper* wrapper_;
|
|
296
|
+
std::vector<uint8_t> buffer_;
|
|
297
|
+
Napi::Promise::Deferred deferred_;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// MergeFrom Worker
|
|
301
|
+
class MergeFromWorker : public Napi::AsyncWorker {
|
|
302
|
+
public:
|
|
303
|
+
MergeFromWorker(FaissIndexWrapper* target, FaissIndexWrapper* source, Napi::Promise::Deferred deferred)
|
|
304
|
+
: Napi::AsyncWorker(deferred.Env(), "MergeFromWorker"),
|
|
305
|
+
target_(target),
|
|
306
|
+
source_(source),
|
|
307
|
+
deferred_(deferred) {
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
void Execute() override {
|
|
311
|
+
try {
|
|
312
|
+
if (target_->IsDisposed()) {
|
|
313
|
+
SetError("Target index has been disposed");
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (source_->IsDisposed()) {
|
|
317
|
+
SetError("Source index has been disposed");
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
target_->MergeFrom(*source_);
|
|
321
|
+
} catch (const std::exception& e) {
|
|
322
|
+
SetError(std::string("FAISS error: ") + e.what());
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
void OnOK() override {
|
|
327
|
+
deferred_.Resolve(Env().Undefined());
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
void OnError(const Napi::Error& e) override {
|
|
331
|
+
deferred_.Reject(e.Value());
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private:
|
|
335
|
+
FaissIndexWrapper* target_;
|
|
336
|
+
FaissIndexWrapper* source_;
|
|
337
|
+
Napi::Promise::Deferred deferred_;
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
// Wrapper class that bridges N-API and our C++ wrapper
|
|
341
|
+
class FaissIndexWrapperJS : public Napi::ObjectWrap<FaissIndexWrapperJS> {
|
|
342
|
+
public:
|
|
343
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
344
|
+
FaissIndexWrapperJS(const Napi::CallbackInfo& info);
|
|
345
|
+
~FaissIndexWrapperJS();
|
|
346
|
+
|
|
347
|
+
private:
|
|
348
|
+
static Napi::FunctionReference constructor;
|
|
349
|
+
std::unique_ptr<FaissIndexWrapper> wrapper_;
|
|
350
|
+
int dims_;
|
|
351
|
+
|
|
352
|
+
// Methods
|
|
353
|
+
Napi::Value Add(const Napi::CallbackInfo& info);
|
|
354
|
+
Napi::Value Train(const Napi::CallbackInfo& info);
|
|
355
|
+
Napi::Value Search(const Napi::CallbackInfo& info);
|
|
356
|
+
Napi::Value SearchBatch(const Napi::CallbackInfo& info);
|
|
357
|
+
Napi::Value GetStats(const Napi::CallbackInfo& info);
|
|
358
|
+
Napi::Value Dispose(const Napi::CallbackInfo& info);
|
|
359
|
+
Napi::Value Save(const Napi::CallbackInfo& info);
|
|
360
|
+
Napi::Value ToBuffer(const Napi::CallbackInfo& info);
|
|
361
|
+
Napi::Value MergeFrom(const Napi::CallbackInfo& info);
|
|
362
|
+
Napi::Value SetNprobe(const Napi::CallbackInfo& info);
|
|
363
|
+
|
|
364
|
+
// Static methods
|
|
365
|
+
static Napi::Value Load(const Napi::CallbackInfo& info);
|
|
366
|
+
static Napi::Value FromBuffer(const Napi::CallbackInfo& info);
|
|
367
|
+
|
|
368
|
+
// Helper methods
|
|
369
|
+
void ValidateNotDisposed(Napi::Env env) const;
|
|
370
|
+
Napi::Float32Array CreateFloat32Array(Napi::Env env, size_t length, const float* data);
|
|
371
|
+
Napi::Int32Array CreateInt32Array(Napi::Env env, size_t length, const faiss::idx_t* data);
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// Static constructor reference
|
|
375
|
+
Napi::FunctionReference FaissIndexWrapperJS::constructor;
|
|
376
|
+
|
|
377
|
+
Napi::Object FaissIndexWrapperJS::Init(Napi::Env env, Napi::Object exports) {
|
|
378
|
+
Napi::Function func = DefineClass(env, "FaissIndexWrapper", {
|
|
379
|
+
InstanceMethod("add", &FaissIndexWrapperJS::Add),
|
|
380
|
+
InstanceMethod("train", &FaissIndexWrapperJS::Train),
|
|
381
|
+
InstanceMethod("search", &FaissIndexWrapperJS::Search),
|
|
382
|
+
InstanceMethod("searchBatch", &FaissIndexWrapperJS::SearchBatch),
|
|
383
|
+
InstanceMethod("getStats", &FaissIndexWrapperJS::GetStats),
|
|
384
|
+
InstanceMethod("dispose", &FaissIndexWrapperJS::Dispose),
|
|
385
|
+
InstanceMethod("save", &FaissIndexWrapperJS::Save),
|
|
386
|
+
InstanceMethod("toBuffer", &FaissIndexWrapperJS::ToBuffer),
|
|
387
|
+
InstanceMethod("mergeFrom", &FaissIndexWrapperJS::MergeFrom),
|
|
388
|
+
InstanceMethod("setNprobe", &FaissIndexWrapperJS::SetNprobe),
|
|
389
|
+
StaticMethod("load", &FaissIndexWrapperJS::Load),
|
|
390
|
+
StaticMethod("fromBuffer", &FaissIndexWrapperJS::FromBuffer),
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
constructor = Napi::Persistent(func);
|
|
394
|
+
constructor.SuppressDestruct();
|
|
395
|
+
|
|
396
|
+
exports.Set("FaissIndexWrapper", func);
|
|
397
|
+
return exports;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
FaissIndexWrapperJS::FaissIndexWrapperJS(const Napi::CallbackInfo& info)
|
|
401
|
+
: Napi::ObjectWrap<FaissIndexWrapperJS>(info), dims_(0) {
|
|
402
|
+
Napi::Env env = info.Env();
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
// Validate constructor arguments
|
|
406
|
+
if (info.Length() < 1) {
|
|
407
|
+
throw Napi::TypeError::New(env, "Expected 1 argument: { dims: number }");
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (!info[0].IsObject()) {
|
|
411
|
+
throw Napi::TypeError::New(env, "Expected object with 'dims' property");
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
Napi::Object config = info[0].As<Napi::Object>();
|
|
415
|
+
|
|
416
|
+
if (!config.Has("dims") || !config.Get("dims").IsNumber()) {
|
|
417
|
+
throw Napi::TypeError::New(env, "Config must have 'dims' as a number");
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
dims_ = config.Get("dims").As<Napi::Number>().Int32Value();
|
|
421
|
+
|
|
422
|
+
if (dims_ <= 0) {
|
|
423
|
+
throw Napi::RangeError::New(env, "Dimensions must be positive");
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Get index type (default to "FLAT_L2" -> "Flat")
|
|
427
|
+
std::string indexDescription = "Flat"; // Default: IndexFlatL2
|
|
428
|
+
int metric = 1; // Default: METRIC_L2
|
|
429
|
+
|
|
430
|
+
if (config.Has("type") && config.Get("type").IsString()) {
|
|
431
|
+
std::string type = config.Get("type").As<Napi::String>().Utf8Value();
|
|
432
|
+
|
|
433
|
+
if (type == "FLAT_L2") {
|
|
434
|
+
indexDescription = "Flat";
|
|
435
|
+
metric = 1; // METRIC_L2
|
|
436
|
+
} else if (type == "IVF_FLAT") {
|
|
437
|
+
// Build IVF string: "IVF{nlist},Flat"
|
|
438
|
+
int nlist = 100; // Default number of clusters
|
|
439
|
+
if (config.Has("nlist") && config.Get("nlist").IsNumber()) {
|
|
440
|
+
nlist = config.Get("nlist").As<Napi::Number>().Int32Value();
|
|
441
|
+
}
|
|
442
|
+
indexDescription = "IVF" + std::to_string(nlist) + ",Flat";
|
|
443
|
+
metric = 1; // METRIC_L2
|
|
444
|
+
} else if (type == "HNSW") {
|
|
445
|
+
// Build HNSW string: "HNSW{M}"
|
|
446
|
+
int M = 16; // Default connections per node
|
|
447
|
+
if (config.Has("M") && config.Get("M").IsNumber()) {
|
|
448
|
+
M = config.Get("M").As<Napi::Number>().Int32Value();
|
|
449
|
+
}
|
|
450
|
+
indexDescription = "HNSW" + std::to_string(M);
|
|
451
|
+
metric = 1; // METRIC_L2
|
|
452
|
+
} else {
|
|
453
|
+
throw Napi::TypeError::New(env, "Unsupported index type: " + type + ". Supported: FLAT_L2, IVF_FLAT, HNSW");
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Create the C++ wrapper with index_factory
|
|
458
|
+
wrapper_ = std::make_unique<FaissIndexWrapper>(dims_, indexDescription, metric);
|
|
459
|
+
|
|
460
|
+
// Set nprobe for IVF indexes
|
|
461
|
+
if (config.Has("nprobe") && config.Get("nprobe").IsNumber()) {
|
|
462
|
+
int nprobe = config.Get("nprobe").As<Napi::Number>().Int32Value();
|
|
463
|
+
wrapper_->SetNprobe(nprobe);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
} catch (const Napi::Error& e) {
|
|
467
|
+
throw; // Re-throw N-API errors
|
|
468
|
+
} catch (const std::exception& e) {
|
|
469
|
+
throw Napi::Error::New(env, std::string("FAISS error: ") + e.what());
|
|
470
|
+
} catch (...) {
|
|
471
|
+
throw Napi::Error::New(env, "Unknown error creating index");
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
FaissIndexWrapperJS::~FaissIndexWrapperJS() {
|
|
476
|
+
// RAII: wrapper_ will be automatically destroyed
|
|
477
|
+
// But we can explicitly dispose if needed
|
|
478
|
+
if (wrapper_ && !wrapper_->IsDisposed()) {
|
|
479
|
+
wrapper_->Dispose();
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
void FaissIndexWrapperJS::ValidateNotDisposed(Napi::Env env) const {
|
|
484
|
+
if (!wrapper_ || wrapper_->IsDisposed()) {
|
|
485
|
+
throw Napi::Error::New(env, "Index has been disposed");
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
Napi::Value FaissIndexWrapperJS::Add(const Napi::CallbackInfo& info) {
|
|
490
|
+
Napi::Env env = info.Env();
|
|
491
|
+
|
|
492
|
+
try {
|
|
493
|
+
ValidateNotDisposed(env);
|
|
494
|
+
|
|
495
|
+
if (info.Length() < 1) {
|
|
496
|
+
throw Napi::TypeError::New(env, "Expected at least 1 argument: vectors (Float32Array)");
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (!info[0].IsTypedArray()) {
|
|
500
|
+
throw Napi::TypeError::New(env, "Expected Float32Array");
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
Napi::TypedArray arr = info[0].As<Napi::TypedArray>();
|
|
504
|
+
|
|
505
|
+
if (arr.TypedArrayType() != napi_float32_array) {
|
|
506
|
+
throw Napi::TypeError::New(env, "Expected Float32Array");
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
Napi::Float32Array floatArr = arr.As<Napi::Float32Array>();
|
|
510
|
+
size_t length = floatArr.ElementLength();
|
|
511
|
+
|
|
512
|
+
// Validate dimensions
|
|
513
|
+
if (length % dims_ != 0) {
|
|
514
|
+
throw Napi::RangeError::New(env,
|
|
515
|
+
"Vector length must be a multiple of dimensions. Got " +
|
|
516
|
+
std::to_string(length) + ", expected multiple of " + std::to_string(dims_));
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
size_t n = length / dims_;
|
|
520
|
+
|
|
521
|
+
// Get pointer to data (zero-copy read) - copy data for async worker
|
|
522
|
+
const float* data = floatArr.Data();
|
|
523
|
+
|
|
524
|
+
// Create promise and async worker
|
|
525
|
+
Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env);
|
|
526
|
+
AddWorker* worker = new AddWorker(wrapper_.get(), data, n, dims_, deferred);
|
|
527
|
+
worker->Queue();
|
|
528
|
+
|
|
529
|
+
return deferred.Promise();
|
|
530
|
+
|
|
531
|
+
} catch (const Napi::Error& e) {
|
|
532
|
+
throw; // Re-throw N-API errors
|
|
533
|
+
} catch (const std::exception& e) {
|
|
534
|
+
throw Napi::Error::New(env, std::string("FAISS error: ") + e.what());
|
|
535
|
+
} catch (...) {
|
|
536
|
+
throw Napi::Error::New(env, "Unknown error in add()");
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
Napi::Value FaissIndexWrapperJS::Train(const Napi::CallbackInfo& info) {
|
|
541
|
+
Napi::Env env = info.Env();
|
|
542
|
+
|
|
543
|
+
try {
|
|
544
|
+
ValidateNotDisposed(env);
|
|
545
|
+
|
|
546
|
+
if (info.Length() < 1) {
|
|
547
|
+
throw Napi::TypeError::New(env, "Expected 1 argument: vectors (Float32Array)");
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (!info[0].IsTypedArray()) {
|
|
551
|
+
throw Napi::TypeError::New(env, "Expected Float32Array");
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
Napi::TypedArray arr = info[0].As<Napi::TypedArray>();
|
|
555
|
+
|
|
556
|
+
if (arr.TypedArrayType() != napi_float32_array) {
|
|
557
|
+
throw Napi::TypeError::New(env, "Expected Float32Array");
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
Napi::Float32Array floatArr = arr.As<Napi::Float32Array>();
|
|
561
|
+
size_t length = floatArr.ElementLength();
|
|
562
|
+
|
|
563
|
+
// Validate dimensions
|
|
564
|
+
if (length % dims_ != 0) {
|
|
565
|
+
throw Napi::RangeError::New(env,
|
|
566
|
+
"Vector length must be a multiple of dimensions. Got " +
|
|
567
|
+
std::to_string(length) + ", expected multiple of " + std::to_string(dims_));
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
size_t n = length / dims_;
|
|
571
|
+
|
|
572
|
+
// Get pointer to data (zero-copy read) - copy data for async worker
|
|
573
|
+
const float* data = floatArr.Data();
|
|
574
|
+
|
|
575
|
+
// Create promise and async worker
|
|
576
|
+
Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env);
|
|
577
|
+
TrainWorker* worker = new TrainWorker(wrapper_.get(), data, n, dims_, deferred);
|
|
578
|
+
worker->Queue();
|
|
579
|
+
|
|
580
|
+
return deferred.Promise();
|
|
581
|
+
|
|
582
|
+
} catch (const Napi::Error& e) {
|
|
583
|
+
throw; // Re-throw N-API errors
|
|
584
|
+
} catch (const std::exception& e) {
|
|
585
|
+
throw Napi::Error::New(env, std::string("FAISS error: ") + e.what());
|
|
586
|
+
} catch (...) {
|
|
587
|
+
throw Napi::Error::New(env, "Unknown error in train()");
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
Napi::Value FaissIndexWrapperJS::SetNprobe(const Napi::CallbackInfo& info) {
|
|
592
|
+
Napi::Env env = info.Env();
|
|
593
|
+
|
|
594
|
+
try {
|
|
595
|
+
ValidateNotDisposed(env);
|
|
596
|
+
|
|
597
|
+
if (info.Length() < 1) {
|
|
598
|
+
throw Napi::TypeError::New(env, "Expected 1 argument: nprobe (number)");
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (!info[0].IsNumber()) {
|
|
602
|
+
throw Napi::TypeError::New(env, "Expected number for nprobe");
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
int nprobe = info[0].As<Napi::Number>().Int32Value();
|
|
606
|
+
|
|
607
|
+
if (nprobe <= 0) {
|
|
608
|
+
throw Napi::RangeError::New(env, "nprobe must be positive");
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
wrapper_->SetNprobe(nprobe);
|
|
612
|
+
return env.Undefined();
|
|
613
|
+
|
|
614
|
+
} catch (const Napi::Error& e) {
|
|
615
|
+
throw; // Re-throw N-API errors
|
|
616
|
+
} catch (const std::exception& e) {
|
|
617
|
+
throw Napi::Error::New(env, std::string("FAISS error: ") + e.what());
|
|
618
|
+
} catch (...) {
|
|
619
|
+
throw Napi::Error::New(env, "Unknown error in setNprobe()");
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
Napi::Value FaissIndexWrapperJS::Search(const Napi::CallbackInfo& info) {
|
|
624
|
+
Napi::Env env = info.Env();
|
|
625
|
+
|
|
626
|
+
try {
|
|
627
|
+
ValidateNotDisposed(env);
|
|
628
|
+
|
|
629
|
+
if (info.Length() < 2) {
|
|
630
|
+
throw Napi::TypeError::New(env, "Expected 2 arguments: query (Float32Array), k (number)");
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (!info[0].IsTypedArray()) {
|
|
634
|
+
throw Napi::TypeError::New(env, "Expected Float32Array for query");
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (!info[1].IsNumber()) {
|
|
638
|
+
throw Napi::TypeError::New(env, "Expected number for k");
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
Napi::TypedArray arr = info[0].As<Napi::TypedArray>();
|
|
642
|
+
if (arr.TypedArrayType() != napi_float32_array) {
|
|
643
|
+
throw Napi::TypeError::New(env, "Expected Float32Array for query");
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
Napi::Float32Array queryArr = arr.As<Napi::Float32Array>();
|
|
647
|
+
int k = info[1].As<Napi::Number>().Int32Value();
|
|
648
|
+
|
|
649
|
+
if (static_cast<int>(queryArr.ElementLength()) != dims_) {
|
|
650
|
+
throw Napi::RangeError::New(env,
|
|
651
|
+
"Query vector length must match index dimensions. Got " +
|
|
652
|
+
std::to_string(queryArr.ElementLength()) + ", expected " + std::to_string(dims_));
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (k <= 0) {
|
|
656
|
+
throw Napi::RangeError::New(env, "k must be positive");
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Get query pointer (zero-copy read) - copy data for async worker
|
|
660
|
+
const float* query = queryArr.Data();
|
|
661
|
+
|
|
662
|
+
// Create promise and async worker
|
|
663
|
+
Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env);
|
|
664
|
+
SearchWorker* worker = new SearchWorker(wrapper_.get(), query, k, deferred);
|
|
665
|
+
worker->Queue();
|
|
666
|
+
|
|
667
|
+
return deferred.Promise();
|
|
668
|
+
|
|
669
|
+
} catch (const Napi::Error& e) {
|
|
670
|
+
throw; // Re-throw N-API errors
|
|
671
|
+
} catch (const std::exception& e) {
|
|
672
|
+
throw Napi::Error::New(env, std::string("FAISS error: ") + e.what());
|
|
673
|
+
} catch (...) {
|
|
674
|
+
throw Napi::Error::New(env, "Unknown error in search()");
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
Napi::Value FaissIndexWrapperJS::SearchBatch(const Napi::CallbackInfo& info) {
|
|
679
|
+
Napi::Env env = info.Env();
|
|
680
|
+
|
|
681
|
+
try {
|
|
682
|
+
ValidateNotDisposed(env);
|
|
683
|
+
|
|
684
|
+
if (info.Length() < 2) {
|
|
685
|
+
throw Napi::TypeError::New(env, "Expected 2 arguments: queries (Float32Array), k (number)");
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
if (!info[0].IsTypedArray()) {
|
|
689
|
+
throw Napi::TypeError::New(env, "Expected Float32Array for queries");
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (!info[1].IsNumber()) {
|
|
693
|
+
throw Napi::TypeError::New(env, "Expected number for k");
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
Napi::TypedArray arr = info[0].As<Napi::TypedArray>();
|
|
697
|
+
if (arr.TypedArrayType() != napi_float32_array) {
|
|
698
|
+
throw Napi::TypeError::New(env, "Expected Float32Array for queries");
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
Napi::Float32Array queriesArr = arr.As<Napi::Float32Array>();
|
|
702
|
+
size_t totalElements = queriesArr.ElementLength();
|
|
703
|
+
int k = info[1].As<Napi::Number>().Int32Value();
|
|
704
|
+
|
|
705
|
+
if (totalElements == 0) {
|
|
706
|
+
throw Napi::RangeError::New(env, "Queries array cannot be empty");
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (totalElements % dims_ != 0) {
|
|
710
|
+
throw Napi::RangeError::New(env,
|
|
711
|
+
"Queries array length must be a multiple of index dimensions. Got " +
|
|
712
|
+
std::to_string(totalElements) + ", expected multiple of " + std::to_string(dims_));
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
size_t nq = totalElements / dims_;
|
|
716
|
+
|
|
717
|
+
if (k <= 0) {
|
|
718
|
+
throw Napi::RangeError::New(env, "k must be positive");
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Get queries pointer (zero-copy read) - copy data for async worker
|
|
722
|
+
const float* queries = queriesArr.Data();
|
|
723
|
+
|
|
724
|
+
// Create promise and async worker
|
|
725
|
+
Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env);
|
|
726
|
+
SearchBatchWorker* worker = new SearchBatchWorker(wrapper_.get(), queries, nq, k, deferred);
|
|
727
|
+
worker->Queue();
|
|
728
|
+
|
|
729
|
+
return deferred.Promise();
|
|
730
|
+
|
|
731
|
+
} catch (const Napi::Error& e) {
|
|
732
|
+
throw; // Re-throw N-API errors
|
|
733
|
+
} catch (const std::exception& e) {
|
|
734
|
+
throw Napi::Error::New(env, std::string("FAISS error: ") + e.what());
|
|
735
|
+
} catch (...) {
|
|
736
|
+
throw Napi::Error::New(env, "Unknown error in searchBatch()");
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
Napi::Value FaissIndexWrapperJS::GetStats(const Napi::CallbackInfo& info) {
|
|
741
|
+
Napi::Env env = info.Env();
|
|
742
|
+
|
|
743
|
+
try {
|
|
744
|
+
ValidateNotDisposed(env);
|
|
745
|
+
|
|
746
|
+
Napi::Object stats = Napi::Object::New(env);
|
|
747
|
+
stats.Set("ntotal", Napi::Number::New(env, wrapper_->GetTotalVectors()));
|
|
748
|
+
stats.Set("dims", Napi::Number::New(env, wrapper_->GetDimensions()));
|
|
749
|
+
stats.Set("isTrained", Napi::Boolean::New(env, wrapper_->IsTrained()));
|
|
750
|
+
stats.Set("type", Napi::String::New(env, "FLAT_L2"));
|
|
751
|
+
|
|
752
|
+
return stats;
|
|
753
|
+
|
|
754
|
+
} catch (const Napi::Error& e) {
|
|
755
|
+
throw;
|
|
756
|
+
} catch (const std::exception& e) {
|
|
757
|
+
throw Napi::Error::New(env, std::string("FAISS error: ") + e.what());
|
|
758
|
+
} catch (...) {
|
|
759
|
+
throw Napi::Error::New(env, "Unknown error in getStats()");
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
Napi::Value FaissIndexWrapperJS::Dispose(const Napi::CallbackInfo& info) {
|
|
764
|
+
Napi::Env env = info.Env();
|
|
765
|
+
|
|
766
|
+
if (wrapper_ && !wrapper_->IsDisposed()) {
|
|
767
|
+
wrapper_->Dispose();
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
return env.Undefined();
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
Napi::Float32Array FaissIndexWrapperJS::CreateFloat32Array(Napi::Env env, size_t length, const float* data) {
|
|
774
|
+
Napi::Float32Array arr = Napi::Float32Array::New(env, length);
|
|
775
|
+
memcpy(arr.Data(), data, length * sizeof(float));
|
|
776
|
+
return arr;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
Napi::Int32Array FaissIndexWrapperJS::CreateInt32Array(Napi::Env env, size_t length, const faiss::idx_t* data) {
|
|
780
|
+
Napi::Int32Array arr = Napi::Int32Array::New(env, length);
|
|
781
|
+
// faiss::idx_t is typically int64_t, but we'll cast to int32_t for JS
|
|
782
|
+
// Use direct pointer access for better performance
|
|
783
|
+
int32_t* arrData = arr.Data();
|
|
784
|
+
for (size_t i = 0; i < length; i++) {
|
|
785
|
+
arrData[i] = static_cast<int32_t>(data[i]);
|
|
786
|
+
}
|
|
787
|
+
return arr;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
Napi::Value FaissIndexWrapperJS::Save(const Napi::CallbackInfo& info) {
|
|
791
|
+
Napi::Env env = info.Env();
|
|
792
|
+
|
|
793
|
+
try {
|
|
794
|
+
ValidateNotDisposed(env);
|
|
795
|
+
|
|
796
|
+
if (info.Length() < 1) {
|
|
797
|
+
throw Napi::TypeError::New(env, "Expected 1 argument: filename (string)");
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (!info[0].IsString()) {
|
|
801
|
+
throw Napi::TypeError::New(env, "Expected string for filename");
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
std::string filename = info[0].As<Napi::String>().Utf8Value();
|
|
805
|
+
|
|
806
|
+
// Create promise and async worker
|
|
807
|
+
Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env);
|
|
808
|
+
SaveWorker* worker = new SaveWorker(wrapper_.get(), filename, deferred);
|
|
809
|
+
worker->Queue();
|
|
810
|
+
|
|
811
|
+
return deferred.Promise();
|
|
812
|
+
|
|
813
|
+
} catch (const Napi::Error& e) {
|
|
814
|
+
throw;
|
|
815
|
+
} catch (const std::exception& e) {
|
|
816
|
+
throw Napi::Error::New(env, std::string("FAISS error: ") + e.what());
|
|
817
|
+
} catch (...) {
|
|
818
|
+
throw Napi::Error::New(env, "Unknown error in save()");
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
Napi::Value FaissIndexWrapperJS::ToBuffer(const Napi::CallbackInfo& info) {
|
|
823
|
+
Napi::Env env = info.Env();
|
|
824
|
+
|
|
825
|
+
try {
|
|
826
|
+
ValidateNotDisposed(env);
|
|
827
|
+
|
|
828
|
+
// Create promise and async worker
|
|
829
|
+
Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env);
|
|
830
|
+
ToBufferWorker* worker = new ToBufferWorker(wrapper_.get(), deferred);
|
|
831
|
+
worker->Queue();
|
|
832
|
+
|
|
833
|
+
return deferred.Promise();
|
|
834
|
+
|
|
835
|
+
} catch (const Napi::Error& e) {
|
|
836
|
+
throw;
|
|
837
|
+
} catch (const std::exception& e) {
|
|
838
|
+
throw Napi::Error::New(env, std::string("FAISS error: ") + e.what());
|
|
839
|
+
} catch (...) {
|
|
840
|
+
throw Napi::Error::New(env, "Unknown error in toBuffer()");
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
Napi::Value FaissIndexWrapperJS::MergeFrom(const Napi::CallbackInfo& info) {
|
|
845
|
+
Napi::Env env = info.Env();
|
|
846
|
+
|
|
847
|
+
try {
|
|
848
|
+
ValidateNotDisposed(env);
|
|
849
|
+
|
|
850
|
+
if (info.Length() < 1) {
|
|
851
|
+
throw Napi::TypeError::New(env, "Expected 1 argument: otherIndex (FaissIndex)");
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
if (!info[0].IsObject()) {
|
|
855
|
+
throw Napi::TypeError::New(env, "Expected FaissIndex object");
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// Get the other index instance
|
|
859
|
+
Napi::Object otherObj = info[0].As<Napi::Object>();
|
|
860
|
+
FaissIndexWrapperJS* otherInstance = Napi::ObjectWrap<FaissIndexWrapperJS>::Unwrap(otherObj);
|
|
861
|
+
|
|
862
|
+
if (!otherInstance || !otherInstance->wrapper_) {
|
|
863
|
+
throw Napi::Error::New(env, "Invalid index object");
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
if (otherInstance->wrapper_->IsDisposed()) {
|
|
867
|
+
throw Napi::Error::New(env, "Cannot merge from disposed index");
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Create promise and async worker
|
|
871
|
+
Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env);
|
|
872
|
+
MergeFromWorker* worker = new MergeFromWorker(wrapper_.get(), otherInstance->wrapper_.get(), deferred);
|
|
873
|
+
worker->Queue();
|
|
874
|
+
|
|
875
|
+
return deferred.Promise();
|
|
876
|
+
|
|
877
|
+
} catch (const Napi::Error& e) {
|
|
878
|
+
throw; // Re-throw N-API errors
|
|
879
|
+
} catch (const std::exception& e) {
|
|
880
|
+
throw Napi::Error::New(env, std::string("FAISS error: ") + e.what());
|
|
881
|
+
} catch (...) {
|
|
882
|
+
throw Napi::Error::New(env, "Unknown error in mergeFrom()");
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
Napi::Value FaissIndexWrapperJS::Load(const Napi::CallbackInfo& info) {
|
|
887
|
+
Napi::Env env = info.Env();
|
|
888
|
+
|
|
889
|
+
try {
|
|
890
|
+
if (info.Length() < 1) {
|
|
891
|
+
throw Napi::TypeError::New(env, "Expected 1 argument: filename (string)");
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
if (!info[0].IsString()) {
|
|
895
|
+
throw Napi::TypeError::New(env, "Expected string for filename");
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
std::string filename = info[0].As<Napi::String>().Utf8Value();
|
|
899
|
+
auto loaded_wrapper = FaissIndexWrapper::Load(filename);
|
|
900
|
+
|
|
901
|
+
// Create new JS instance with dummy config (will be replaced)
|
|
902
|
+
int dims = loaded_wrapper->GetDimensions();
|
|
903
|
+
Napi::Object config = Napi::Object::New(env);
|
|
904
|
+
config.Set("dims", Napi::Number::New(env, dims));
|
|
905
|
+
Napi::Object obj = constructor.New({config});
|
|
906
|
+
FaissIndexWrapperJS* instance = Napi::ObjectWrap<FaissIndexWrapperJS>::Unwrap(obj);
|
|
907
|
+
|
|
908
|
+
// Replace the wrapper with the loaded one
|
|
909
|
+
instance->wrapper_ = std::move(loaded_wrapper);
|
|
910
|
+
instance->dims_ = dims;
|
|
911
|
+
|
|
912
|
+
return obj;
|
|
913
|
+
|
|
914
|
+
} catch (const Napi::Error& e) {
|
|
915
|
+
throw;
|
|
916
|
+
} catch (const std::exception& e) {
|
|
917
|
+
throw Napi::Error::New(env, std::string("FAISS error: ") + e.what());
|
|
918
|
+
} catch (...) {
|
|
919
|
+
throw Napi::Error::New(env, "Unknown error in load()");
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
Napi::Value FaissIndexWrapperJS::FromBuffer(const Napi::CallbackInfo& info) {
|
|
924
|
+
Napi::Env env = info.Env();
|
|
925
|
+
|
|
926
|
+
try {
|
|
927
|
+
if (info.Length() < 1) {
|
|
928
|
+
throw Napi::TypeError::New(env, "Expected 1 argument: buffer (Buffer)");
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
if (!info[0].IsBuffer()) {
|
|
932
|
+
throw Napi::TypeError::New(env, "Expected Buffer");
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
Napi::Buffer<uint8_t> buffer = info[0].As<Napi::Buffer<uint8_t>>();
|
|
936
|
+
const uint8_t* data = buffer.Data();
|
|
937
|
+
size_t length = buffer.Length();
|
|
938
|
+
|
|
939
|
+
auto loaded_wrapper = FaissIndexWrapper::FromBuffer(data, length);
|
|
940
|
+
|
|
941
|
+
// Create new JS instance with dummy config (will be replaced)
|
|
942
|
+
int dims = loaded_wrapper->GetDimensions();
|
|
943
|
+
Napi::Object config = Napi::Object::New(env);
|
|
944
|
+
config.Set("dims", Napi::Number::New(env, dims));
|
|
945
|
+
Napi::Object obj = constructor.New({config});
|
|
946
|
+
FaissIndexWrapperJS* instance = Napi::ObjectWrap<FaissIndexWrapperJS>::Unwrap(obj);
|
|
947
|
+
|
|
948
|
+
// Replace the wrapper with the loaded one
|
|
949
|
+
instance->wrapper_ = std::move(loaded_wrapper);
|
|
950
|
+
instance->dims_ = dims;
|
|
951
|
+
|
|
952
|
+
return obj;
|
|
953
|
+
|
|
954
|
+
} catch (const Napi::Error& e) {
|
|
955
|
+
throw;
|
|
956
|
+
} catch (const std::exception& e) {
|
|
957
|
+
throw Napi::Error::New(env, std::string("FAISS error: ") + e.what());
|
|
958
|
+
} catch (...) {
|
|
959
|
+
throw Napi::Error::New(env, "Unknown error in fromBuffer()");
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// Module initialization
|
|
964
|
+
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
965
|
+
FaissIndexWrapperJS::Init(env, exports);
|
|
966
|
+
return exports;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
NODE_API_MODULE(faiss_node, Init)
|