@taladb/react-native 0.6.1 → 0.7.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/android/src/main/java/com/taladb/TalaDBModule.kt +33 -0
- package/android/src/main/jniLibs/arm64-v8a/libredb-0944877d0a949895.so +0 -0
- package/android/src/main/jniLibs/arm64-v8a/libtaladb_ffi.so +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/{libredb-6543807032a4d3d4.so → libredb-3eb5d788992b33b3.so} +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/libtaladb_ffi.so +0 -0
- package/android/src/main/jniLibs/x86_64/libredb-ece86615cd326687.so +0 -0
- package/android/src/main/jniLibs/x86_64/libtaladb_ffi.so +0 -0
- package/cpp/TalaDBHostObject.cpp +340 -0
- package/cpp/TalaDBHostObject.h +17 -0
- package/cpp/taladb.h +94 -0
- package/ios/TalaDB.mm +35 -0
- package/ios/TalaDBFfi.xcframework/Info.plist +9 -9
- package/ios/TalaDBFfi.xcframework/ios-arm64/Headers/TalaDBHostObject.cpp +340 -0
- package/ios/TalaDBFfi.xcframework/ios-arm64/Headers/TalaDBHostObject.h +17 -0
- package/ios/TalaDBFfi.xcframework/ios-arm64/Headers/taladb.h +94 -0
- package/ios/TalaDBFfi.xcframework/ios-arm64/libtaladb_ffi.a +0 -0
- package/ios/TalaDBFfi.xcframework/ios-arm64_x86_64-simulator/Headers/TalaDBHostObject.cpp +340 -0
- package/ios/TalaDBFfi.xcframework/ios-arm64_x86_64-simulator/Headers/TalaDBHostObject.h +17 -0
- package/ios/TalaDBFfi.xcframework/ios-arm64_x86_64-simulator/Headers/taladb.h +94 -0
- package/ios/TalaDBFfi.xcframework/ios-arm64_x86_64-simulator/libtaladb_ffi_sim.a +0 -0
- package/package.json +1 -1
- package/src/NativeTalaDB.ts +45 -0
- package/android/src/main/jniLibs/arm64-v8a/libredb-5d2d0997ccb02a9b.so +0 -0
- package/android/src/main/jniLibs/x86_64/libredb-21fa12fa517068ae.so +0 -0
|
@@ -125,4 +125,37 @@ class TalaDBModule(private val reactContext: ReactApplicationContext) :
|
|
|
125
125
|
override fun dropIndex(collection: String, field: String) {}
|
|
126
126
|
override fun createFtsIndex(collection: String, field: String) {}
|
|
127
127
|
override fun dropFtsIndex(collection: String, field: String) {}
|
|
128
|
+
|
|
129
|
+
// Vector index + findNearest — JSI-only; stubs satisfy Codegen.
|
|
130
|
+
|
|
131
|
+
override fun createVectorIndex(
|
|
132
|
+
collection: String,
|
|
133
|
+
field: String,
|
|
134
|
+
dimensions: Double,
|
|
135
|
+
opts: ReadableMap?,
|
|
136
|
+
) {}
|
|
137
|
+
override fun dropVectorIndex(collection: String, field: String) {}
|
|
138
|
+
override fun upgradeVectorIndex(collection: String, field: String) {}
|
|
139
|
+
override fun findNearest(
|
|
140
|
+
collection: String,
|
|
141
|
+
field: String,
|
|
142
|
+
query: ReadableArray,
|
|
143
|
+
topK: Double,
|
|
144
|
+
filter: ReadableMap?,
|
|
145
|
+
): WritableArray = WritableNativeArray()
|
|
146
|
+
|
|
147
|
+
override fun findNearestAsync(
|
|
148
|
+
collection: String,
|
|
149
|
+
field: String,
|
|
150
|
+
query: ReadableArray,
|
|
151
|
+
topK: Double,
|
|
152
|
+
filter: ReadableMap?,
|
|
153
|
+
promise: Promise,
|
|
154
|
+
) {
|
|
155
|
+
promise.resolve(WritableNativeArray())
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
override fun findAsync(collection: String, filter: ReadableMap?, promise: Promise) {
|
|
159
|
+
promise.resolve(WritableNativeArray())
|
|
160
|
+
}
|
|
128
161
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/cpp/TalaDBHostObject.cpp
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#include "TalaDBHostObject.h"
|
|
2
2
|
|
|
3
|
+
#include <memory>
|
|
3
4
|
#include <stdexcept>
|
|
4
5
|
#include <vector>
|
|
5
6
|
|
|
@@ -7,6 +8,28 @@ using namespace facebook::jsi;
|
|
|
7
8
|
|
|
8
9
|
namespace taladb {
|
|
9
10
|
|
|
11
|
+
namespace {
|
|
12
|
+
|
|
13
|
+
// Read the error string set by the most recent FFI call. Safe to call even
|
|
14
|
+
// when no error has been set — returns an empty string in that case.
|
|
15
|
+
std::string lastError() {
|
|
16
|
+
const char *msg = taladb_last_error();
|
|
17
|
+
return msg ? std::string(msg) : std::string();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Build a JSError with the FFI last-error message appended.
|
|
21
|
+
JSError ffiError(Runtime &rt, const char *prefix) {
|
|
22
|
+
std::string msg = prefix;
|
|
23
|
+
std::string last = lastError();
|
|
24
|
+
if (!last.empty()) {
|
|
25
|
+
msg += ": ";
|
|
26
|
+
msg += last;
|
|
27
|
+
}
|
|
28
|
+
return JSError(rt, msg);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
} // namespace
|
|
32
|
+
|
|
10
33
|
// ---------------------------------------------------------------------------
|
|
11
34
|
// Constructor / Destructor
|
|
12
35
|
// ---------------------------------------------------------------------------
|
|
@@ -57,6 +80,159 @@ std::string TalaDBHostObject::valueToFilterJson(Runtime &rt, const Value &val) {
|
|
|
57
80
|
return stringify(rt, val);
|
|
58
81
|
}
|
|
59
82
|
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Float32Array fast path
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
void TalaDBHostObject::extractF32Query(Runtime &rt, const Value &val,
|
|
88
|
+
std::vector<float> &out) {
|
|
89
|
+
if (!val.isObject()) {
|
|
90
|
+
throw JSError(rt, "query must be a Float32Array or number[]");
|
|
91
|
+
}
|
|
92
|
+
auto obj = val.getObject(rt);
|
|
93
|
+
|
|
94
|
+
// --- Fast path: Float32Array — zero-copy read from the underlying ArrayBuffer ---
|
|
95
|
+
if (obj.isArrayBuffer(rt)) {
|
|
96
|
+
// Raw ArrayBuffer of bytes (length must be a multiple of 4)
|
|
97
|
+
auto ab = obj.getArrayBuffer(rt);
|
|
98
|
+
size_t size = ab.size(rt);
|
|
99
|
+
if (size % sizeof(float) != 0) {
|
|
100
|
+
throw JSError(rt, "ArrayBuffer length is not a multiple of 4 bytes");
|
|
101
|
+
}
|
|
102
|
+
size_t n = size / sizeof(float);
|
|
103
|
+
out.resize(n);
|
|
104
|
+
std::memcpy(out.data(), ab.data(rt), size);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Float32Array exposes `.buffer`, `.byteOffset`, `.byteLength`. We read
|
|
109
|
+
// those directly and memcpy from the underlying ArrayBuffer — no JSON,
|
|
110
|
+
// no per-element Value conversion.
|
|
111
|
+
if (obj.hasProperty(rt, "BYTES_PER_ELEMENT")) {
|
|
112
|
+
auto bpe = obj.getProperty(rt, "BYTES_PER_ELEMENT");
|
|
113
|
+
if (bpe.isNumber() && (int)bpe.getNumber() == 4) {
|
|
114
|
+
auto buf = obj.getPropertyAsObject(rt, "buffer");
|
|
115
|
+
auto ab = buf.getArrayBuffer(rt);
|
|
116
|
+
auto offsetV = obj.getProperty(rt, "byteOffset");
|
|
117
|
+
auto lengthV = obj.getProperty(rt, "byteLength");
|
|
118
|
+
size_t offset = offsetV.isNumber() ? (size_t)offsetV.getNumber() : 0;
|
|
119
|
+
size_t length = lengthV.isNumber() ? (size_t)lengthV.getNumber() : 0;
|
|
120
|
+
if (length % sizeof(float) != 0) {
|
|
121
|
+
throw JSError(rt, "typed-array byteLength is not a multiple of 4");
|
|
122
|
+
}
|
|
123
|
+
size_t n = length / sizeof(float);
|
|
124
|
+
out.resize(n);
|
|
125
|
+
std::memcpy(out.data(), ab.data(rt) + offset, length);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// --- Fallback: number[] — per-element conversion (slow, but correct) ---
|
|
131
|
+
if (obj.isArray(rt)) {
|
|
132
|
+
auto arr = obj.getArray(rt);
|
|
133
|
+
size_t len = arr.size(rt);
|
|
134
|
+
out.resize(len);
|
|
135
|
+
for (size_t i = 0; i < len; i++) {
|
|
136
|
+
auto v = arr.getValueAtIndex(rt, i);
|
|
137
|
+
if (!v.isNumber()) {
|
|
138
|
+
throw JSError(rt, "query array must contain only numbers");
|
|
139
|
+
}
|
|
140
|
+
out[i] = (float)v.getNumber();
|
|
141
|
+
}
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
throw JSError(rt, "query must be a Float32Array, typed array, or number[]");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// Async job → Promise bridge
|
|
150
|
+
//
|
|
151
|
+
// JSI on RN does not give us a thread-safe way to resolve a Promise from a
|
|
152
|
+
// background OS thread. We poll the job handle from the JS thread using
|
|
153
|
+
// `setImmediate`, which costs ~0.1 ms per tick — negligible next to a
|
|
154
|
+
// hundred-millisecond vector search — and the DB work itself runs on the
|
|
155
|
+
// background worker the FFI spawned.
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
Value TalaDBHostObject::awaitJobAsPromise(Runtime &rt, TalaDbJob *job, bool parseAsJson) {
|
|
159
|
+
if (!job) {
|
|
160
|
+
throw ffiError(rt, "failed to start background job");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
auto promiseCtor = rt.global().getPropertyAsFunction(rt, "Promise");
|
|
164
|
+
|
|
165
|
+
// The job pointer is owned by the executor closure; we null it out on
|
|
166
|
+
// consumption to prevent double-free.
|
|
167
|
+
auto jobBox = std::make_shared<TalaDbJob *>(job);
|
|
168
|
+
|
|
169
|
+
auto executor = Function::createFromHostFunction(
|
|
170
|
+
rt, PropNameID::forAscii(rt, "taladbJobExecutor"), 2,
|
|
171
|
+
[jobBox, parseAsJson](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
172
|
+
if (count < 2) {
|
|
173
|
+
return Value::undefined();
|
|
174
|
+
}
|
|
175
|
+
auto resolve = std::make_shared<Function>(args[0].getObject(rt).getFunction(rt));
|
|
176
|
+
auto reject = std::make_shared<Function>(args[1].getObject(rt).getFunction(rt));
|
|
177
|
+
|
|
178
|
+
// `setImmediate` is provided by React Native. Recurse until the
|
|
179
|
+
// worker thread finishes, then take the result on the JS thread.
|
|
180
|
+
auto setImmediate = rt.global().getPropertyAsFunction(rt, "setImmediate");
|
|
181
|
+
auto setImmediateShared = std::make_shared<Function>(std::move(setImmediate));
|
|
182
|
+
|
|
183
|
+
// Capture a self-reference so the polling lambda can schedule itself.
|
|
184
|
+
auto poller = std::make_shared<std::function<void(Runtime &)>>();
|
|
185
|
+
*poller = [jobBox, resolve, reject, setImmediateShared, poller, parseAsJson](Runtime &rt) {
|
|
186
|
+
TalaDbJob *j = *jobBox;
|
|
187
|
+
if (!j) {
|
|
188
|
+
return; // already consumed — defensive
|
|
189
|
+
}
|
|
190
|
+
int32_t state = taladb_job_poll(j);
|
|
191
|
+
if (state == 0) {
|
|
192
|
+
// Still running — reschedule.
|
|
193
|
+
auto tick = Function::createFromHostFunction(
|
|
194
|
+
rt, PropNameID::forAscii(rt, "taladbJobTick"), 0,
|
|
195
|
+
[poller](Runtime &rt, const Value &, const Value *, size_t) -> Value {
|
|
196
|
+
(*poller)(rt);
|
|
197
|
+
return Value::undefined();
|
|
198
|
+
});
|
|
199
|
+
setImmediateShared->call(rt, tick);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Done — take the result and clear the job slot.
|
|
204
|
+
char *raw = taladb_job_take_result(j);
|
|
205
|
+
*jobBox = nullptr;
|
|
206
|
+
|
|
207
|
+
if (!raw) {
|
|
208
|
+
std::string last = lastError();
|
|
209
|
+
auto err = String::createFromUtf8(
|
|
210
|
+
rt, last.empty() ? "taladb job failed" : last);
|
|
211
|
+
reject->call(rt, err);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
std::string json(raw);
|
|
216
|
+
taladb_free_string(raw);
|
|
217
|
+
|
|
218
|
+
if (parseAsJson) {
|
|
219
|
+
try {
|
|
220
|
+
resolve->call(rt, parse(rt, json));
|
|
221
|
+
} catch (const JSError &e) {
|
|
222
|
+
reject->call(rt, String::createFromUtf8(rt, e.getMessage()));
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
resolve->call(rt, String::createFromUtf8(rt, json));
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
(*poller)(rt);
|
|
230
|
+
return Value::undefined();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
return promiseCtor.callAsConstructor(rt, executor);
|
|
234
|
+
}
|
|
235
|
+
|
|
60
236
|
// ---------------------------------------------------------------------------
|
|
61
237
|
// Property names advertised to JS
|
|
62
238
|
// ---------------------------------------------------------------------------
|
|
@@ -70,6 +246,10 @@ std::vector<PropNameID> TalaDBHostObject::getPropertyNames(Runtime &rt) {
|
|
|
70
246
|
"count",
|
|
71
247
|
"createIndex", "dropIndex",
|
|
72
248
|
"createFtsIndex", "dropFtsIndex",
|
|
249
|
+
"createVectorIndex", "dropVectorIndex", "upgradeVectorIndex",
|
|
250
|
+
"findNearest", "findNearestAsync",
|
|
251
|
+
"findAsync",
|
|
252
|
+
"compact",
|
|
73
253
|
"close",
|
|
74
254
|
};
|
|
75
255
|
std::vector<PropNameID> result;
|
|
@@ -264,6 +444,166 @@ Value TalaDBHostObject::get(Runtime &rt, const PropNameID &propName) {
|
|
|
264
444
|
});
|
|
265
445
|
}
|
|
266
446
|
|
|
447
|
+
// ------------------------------------------------------------------
|
|
448
|
+
// createVectorIndex(collection, field, dimensions, opts?): void
|
|
449
|
+
// opts: { metric?: 'cosine'|'dot'|'euclidean', hnsw?: HnswOptions }
|
|
450
|
+
// ------------------------------------------------------------------
|
|
451
|
+
if (name == "createVectorIndex") {
|
|
452
|
+
return Function::createFromHostFunction(
|
|
453
|
+
rt, PropNameID::forAscii(rt, "createVectorIndex"), 4,
|
|
454
|
+
[this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
455
|
+
if (count < 3) throw JSError(rt, "createVectorIndex requires (collection, field, dimensions, opts?)");
|
|
456
|
+
auto col = args[0].getString(rt).utf8(rt);
|
|
457
|
+
auto field = args[1].getString(rt).utf8(rt);
|
|
458
|
+
if (!args[2].isNumber()) throw JSError(rt, "dimensions must be a number");
|
|
459
|
+
size_t dims = (size_t)args[2].getNumber();
|
|
460
|
+
|
|
461
|
+
std::string metricStr;
|
|
462
|
+
std::string hnswJson;
|
|
463
|
+
const char *metricPtr = nullptr;
|
|
464
|
+
const char *hnswPtr = nullptr;
|
|
465
|
+
if (count > 3 && args[3].isObject()) {
|
|
466
|
+
auto opts = args[3].getObject(rt);
|
|
467
|
+
if (opts.hasProperty(rt, "metric")) {
|
|
468
|
+
auto m = opts.getProperty(rt, "metric");
|
|
469
|
+
if (m.isString()) {
|
|
470
|
+
metricStr = m.getString(rt).utf8(rt);
|
|
471
|
+
metricPtr = metricStr.c_str();
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (opts.hasProperty(rt, "hnsw")) {
|
|
475
|
+
auto h = opts.getProperty(rt, "hnsw");
|
|
476
|
+
if (h.isObject()) {
|
|
477
|
+
hnswJson = stringify(rt, h);
|
|
478
|
+
hnswPtr = hnswJson.c_str();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
int32_t res = taladb_create_vector_index(
|
|
484
|
+
db_, col.c_str(), field.c_str(), dims, metricPtr, hnswPtr);
|
|
485
|
+
if (res < 0) throw ffiError(rt, "taladb_create_vector_index failed");
|
|
486
|
+
return Value::undefined();
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// ------------------------------------------------------------------
|
|
491
|
+
// dropVectorIndex(collection, field): void
|
|
492
|
+
// ------------------------------------------------------------------
|
|
493
|
+
if (name == "dropVectorIndex") {
|
|
494
|
+
return Function::createFromHostFunction(
|
|
495
|
+
rt, PropNameID::forAscii(rt, "dropVectorIndex"), 2,
|
|
496
|
+
[this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
497
|
+
if (count < 2) throw JSError(rt, "dropVectorIndex requires 2 arguments");
|
|
498
|
+
auto col = args[0].getString(rt).utf8(rt);
|
|
499
|
+
auto field = args[1].getString(rt).utf8(rt);
|
|
500
|
+
int32_t res = taladb_drop_vector_index(db_, col.c_str(), field.c_str());
|
|
501
|
+
if (res < 0) throw ffiError(rt, "taladb_drop_vector_index failed");
|
|
502
|
+
return Value::undefined();
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// ------------------------------------------------------------------
|
|
507
|
+
// upgradeVectorIndex(collection, field): void
|
|
508
|
+
// ------------------------------------------------------------------
|
|
509
|
+
if (name == "upgradeVectorIndex") {
|
|
510
|
+
return Function::createFromHostFunction(
|
|
511
|
+
rt, PropNameID::forAscii(rt, "upgradeVectorIndex"), 2,
|
|
512
|
+
[this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
513
|
+
if (count < 2) throw JSError(rt, "upgradeVectorIndex requires 2 arguments");
|
|
514
|
+
auto col = args[0].getString(rt).utf8(rt);
|
|
515
|
+
auto field = args[1].getString(rt).utf8(rt);
|
|
516
|
+
int32_t res = taladb_upgrade_vector_index(db_, col.c_str(), field.c_str());
|
|
517
|
+
if (res < 0) throw ffiError(rt, "taladb_upgrade_vector_index failed");
|
|
518
|
+
return Value::undefined();
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// ------------------------------------------------------------------
|
|
523
|
+
// findNearest(collection, field, query, topK, filter?): { document, score }[]
|
|
524
|
+
// query — Float32Array (preferred, zero-copy) or number[]
|
|
525
|
+
// ------------------------------------------------------------------
|
|
526
|
+
if (name == "findNearest") {
|
|
527
|
+
return Function::createFromHostFunction(
|
|
528
|
+
rt, PropNameID::forAscii(rt, "findNearest"), 5,
|
|
529
|
+
[this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
530
|
+
if (count < 4) throw JSError(rt, "findNearest requires (collection, field, query, topK, filter?)");
|
|
531
|
+
auto col = args[0].getString(rt).utf8(rt);
|
|
532
|
+
auto field = args[1].getString(rt).utf8(rt);
|
|
533
|
+
std::vector<float> qbuf;
|
|
534
|
+
extractF32Query(rt, args[2], qbuf);
|
|
535
|
+
if (!args[3].isNumber()) throw JSError(rt, "topK must be a number");
|
|
536
|
+
size_t topK = (size_t)args[3].getNumber();
|
|
537
|
+
std::string filterJson = count > 4
|
|
538
|
+
? valueToFilterJson(rt, args[4])
|
|
539
|
+
: std::string();
|
|
540
|
+
const char *filterPtr = count > 4 ? filterJson.c_str() : nullptr;
|
|
541
|
+
|
|
542
|
+
char *result = taladb_find_nearest(
|
|
543
|
+
db_, col.c_str(), field.c_str(),
|
|
544
|
+
qbuf.data(), qbuf.size(), topK, filterPtr);
|
|
545
|
+
if (!result) throw ffiError(rt, "taladb_find_nearest failed");
|
|
546
|
+
std::string json(result);
|
|
547
|
+
taladb_free_string(result);
|
|
548
|
+
return parse(rt, json);
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// ------------------------------------------------------------------
|
|
553
|
+
// findNearestAsync(...) → Promise<{ document, score }[]>
|
|
554
|
+
// Runs on a background thread. JS thread polls via setImmediate.
|
|
555
|
+
// ------------------------------------------------------------------
|
|
556
|
+
if (name == "findNearestAsync") {
|
|
557
|
+
return Function::createFromHostFunction(
|
|
558
|
+
rt, PropNameID::forAscii(rt, "findNearestAsync"), 5,
|
|
559
|
+
[this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
560
|
+
if (count < 4) throw JSError(rt, "findNearestAsync requires (collection, field, query, topK, filter?)");
|
|
561
|
+
auto col = args[0].getString(rt).utf8(rt);
|
|
562
|
+
auto field = args[1].getString(rt).utf8(rt);
|
|
563
|
+
std::vector<float> qbuf;
|
|
564
|
+
extractF32Query(rt, args[2], qbuf);
|
|
565
|
+
if (!args[3].isNumber()) throw JSError(rt, "topK must be a number");
|
|
566
|
+
size_t topK = (size_t)args[3].getNumber();
|
|
567
|
+
std::string filterJson = count > 4
|
|
568
|
+
? valueToFilterJson(rt, args[4])
|
|
569
|
+
: std::string();
|
|
570
|
+
const char *filterPtr = count > 4 ? filterJson.c_str() : nullptr;
|
|
571
|
+
|
|
572
|
+
TalaDbJob *job = taladb_find_nearest_start(
|
|
573
|
+
db_, col.c_str(), field.c_str(),
|
|
574
|
+
qbuf.data(), qbuf.size(), topK, filterPtr);
|
|
575
|
+
return awaitJobAsPromise(rt, job, /*parseAsJson=*/true);
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// ------------------------------------------------------------------
|
|
580
|
+
// findAsync(collection, filter?) → Promise<object[]>
|
|
581
|
+
// ------------------------------------------------------------------
|
|
582
|
+
if (name == "findAsync") {
|
|
583
|
+
return Function::createFromHostFunction(
|
|
584
|
+
rt, PropNameID::forAscii(rt, "findAsync"), 2,
|
|
585
|
+
[this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
586
|
+
if (count < 1) throw JSError(rt, "findAsync requires at least 1 argument");
|
|
587
|
+
auto col = args[0].getString(rt).utf8(rt);
|
|
588
|
+
auto filterJson = count > 1 ? valueToFilterJson(rt, args[1]) : std::string("{}");
|
|
589
|
+
TalaDbJob *job = taladb_find_start(db_, col.c_str(), filterJson.c_str());
|
|
590
|
+
return awaitJobAsPromise(rt, job, /*parseAsJson=*/true);
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// ------------------------------------------------------------------
|
|
595
|
+
// compact(): void
|
|
596
|
+
// ------------------------------------------------------------------
|
|
597
|
+
if (name == "compact") {
|
|
598
|
+
return Function::createFromHostFunction(
|
|
599
|
+
rt, PropNameID::forAscii(rt, "compact"), 0,
|
|
600
|
+
[this](Runtime &rt, const Value &, const Value *, size_t) -> Value {
|
|
601
|
+
int32_t res = taladb_compact(db_);
|
|
602
|
+
if (res < 0) throw JSError(rt, "taladb_compact failed");
|
|
603
|
+
return Value::undefined();
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
|
|
267
607
|
// ------------------------------------------------------------------
|
|
268
608
|
// close(): void (synchronous — the destructor does the real work)
|
|
269
609
|
// ------------------------------------------------------------------
|
package/cpp/TalaDBHostObject.h
CHANGED
|
@@ -50,6 +50,23 @@ private:
|
|
|
50
50
|
// Returns "{}" when the value is null/undefined.
|
|
51
51
|
static std::string valueToFilterJson(facebook::jsi::Runtime &rt,
|
|
52
52
|
const facebook::jsi::Value &val);
|
|
53
|
+
|
|
54
|
+
// ------------------------------------------------------------------
|
|
55
|
+
// Float32Array helpers — zero-copy extraction from JSI
|
|
56
|
+
// ------------------------------------------------------------------
|
|
57
|
+
//
|
|
58
|
+
// Accepts a JS `Float32Array` *or* a plain `number[]`. Populates `out`
|
|
59
|
+
// with a copy of the values (kept alive by the caller for the FFI call).
|
|
60
|
+
// Throws JSError when the value is neither.
|
|
61
|
+
static void extractF32Query(facebook::jsi::Runtime &rt,
|
|
62
|
+
const facebook::jsi::Value &val,
|
|
63
|
+
std::vector<float> &out);
|
|
64
|
+
|
|
65
|
+
// Poll a TalaDbJob from the JS thread via setImmediate until done,
|
|
66
|
+
// then resolve / reject a Promise with the result / error.
|
|
67
|
+
static facebook::jsi::Value awaitJobAsPromise(facebook::jsi::Runtime &rt,
|
|
68
|
+
TalaDbJob *job,
|
|
69
|
+
bool parseAsJson);
|
|
53
70
|
};
|
|
54
71
|
|
|
55
72
|
} // namespace taladb
|
package/cpp/taladb.h
CHANGED
|
@@ -48,6 +48,12 @@ TalaDbHandle *taladb_open_with_config(const char *path, const char *config_json)
|
|
|
48
48
|
/** Flush and close the database, freeing the handle. */
|
|
49
49
|
void taladb_close(TalaDbHandle *handle);
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Compact the underlying storage file, reclaiming space freed by deletes and
|
|
53
|
+
* updates. No-op on in-memory databases. Returns 1 on success, -1 on error.
|
|
54
|
+
*/
|
|
55
|
+
int32_t taladb_compact(TalaDbHandle *handle);
|
|
56
|
+
|
|
51
57
|
/** Free a C string returned by any taladb_* function. */
|
|
52
58
|
void taladb_free_string(char *s);
|
|
53
59
|
|
|
@@ -144,6 +150,94 @@ void taladb_drop_index (TalaDbHandle *handle, const char *collection, const
|
|
|
144
150
|
void taladb_create_fts_index(TalaDbHandle *handle, const char *collection, const char *field);
|
|
145
151
|
void taladb_drop_fts_index (TalaDbHandle *handle, const char *collection, const char *field);
|
|
146
152
|
|
|
153
|
+
/* -------------------------------------------------------------------------
|
|
154
|
+
* Vector index management
|
|
155
|
+
*
|
|
156
|
+
* metric — "cosine" (default), "dot", or "euclidean". NULL ⇒ cosine.
|
|
157
|
+
* hnsw_json — JSON-encoded HnswOptions, or NULL for flat index.
|
|
158
|
+
* ---------------------------------------------------------------------- */
|
|
159
|
+
|
|
160
|
+
int32_t taladb_create_vector_index(TalaDbHandle *handle,
|
|
161
|
+
const char *collection,
|
|
162
|
+
const char *field,
|
|
163
|
+
size_t dimensions,
|
|
164
|
+
const char *metric,
|
|
165
|
+
const char *hnsw_json);
|
|
166
|
+
|
|
167
|
+
int32_t taladb_drop_vector_index (TalaDbHandle *handle, const char *collection, const char *field);
|
|
168
|
+
int32_t taladb_upgrade_vector_index(TalaDbHandle *handle, const char *collection, const char *field);
|
|
169
|
+
|
|
170
|
+
/* -------------------------------------------------------------------------
|
|
171
|
+
* findNearest — Float32 zero-copy fast path
|
|
172
|
+
*
|
|
173
|
+
* query_ptr / query_len address `query_len` consecutive f32 values. Caller
|
|
174
|
+
* retains ownership; the buffer may be freed immediately after the call.
|
|
175
|
+
* filter_json may be NULL / "{}" / "null" to search without a pre-filter.
|
|
176
|
+
*
|
|
177
|
+
* Returns a JSON array string `[{document, score}, ...]`, or NULL on error.
|
|
178
|
+
* Caller must free with taladb_free_string.
|
|
179
|
+
* ---------------------------------------------------------------------- */
|
|
180
|
+
|
|
181
|
+
char *taladb_find_nearest(TalaDbHandle *handle,
|
|
182
|
+
const char *collection,
|
|
183
|
+
const char *field,
|
|
184
|
+
const float *query_ptr,
|
|
185
|
+
size_t query_len,
|
|
186
|
+
size_t top_k,
|
|
187
|
+
const char *filter_json);
|
|
188
|
+
|
|
189
|
+
/* -------------------------------------------------------------------------
|
|
190
|
+
* Async job API — run heavy queries on a background thread.
|
|
191
|
+
*
|
|
192
|
+
* Flow
|
|
193
|
+
* ----
|
|
194
|
+
* TalaDbJob *j = taladb_find_nearest_start(...); // spawns worker thread
|
|
195
|
+
* while (taladb_job_poll(j) == 0) { /* yield to JS event loop */ }
|
|
196
|
+
* char *json = taladb_job_take_result(j); // frees the job
|
|
197
|
+
*
|
|
198
|
+
* Lifetime contract
|
|
199
|
+
* -----------------
|
|
200
|
+
* The handle passed to `*_start` MUST remain valid until the job has been
|
|
201
|
+
* taken (via take_result) or cancelled (via cancel). The C++ HostObject
|
|
202
|
+
* enforces this by not calling taladb_close while jobs are outstanding.
|
|
203
|
+
* ---------------------------------------------------------------------- */
|
|
204
|
+
|
|
205
|
+
typedef struct TalaDbJob TalaDbJob;
|
|
206
|
+
|
|
207
|
+
/** Kick a background `find_nearest`. Returns NULL on immediate arg error. */
|
|
208
|
+
TalaDbJob *taladb_find_nearest_start(TalaDbHandle *handle,
|
|
209
|
+
const char *collection,
|
|
210
|
+
const char *field,
|
|
211
|
+
const float *query_ptr,
|
|
212
|
+
size_t query_len,
|
|
213
|
+
size_t top_k,
|
|
214
|
+
const char *filter_json);
|
|
215
|
+
|
|
216
|
+
/** Kick a background `find`. Returns NULL on immediate arg error. */
|
|
217
|
+
TalaDbJob *taladb_find_start(TalaDbHandle *handle,
|
|
218
|
+
const char *collection,
|
|
219
|
+
const char *filter_json);
|
|
220
|
+
|
|
221
|
+
/** Non-blocking. Returns 1 if complete, 0 if still running, -1 on NULL job. */
|
|
222
|
+
int32_t taladb_job_poll(TalaDbJob *job);
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Join the worker, take its result, and free the job.
|
|
226
|
+
* Returns a JSON string on success, or NULL on error (see taladb_last_error).
|
|
227
|
+
* Caller must free with taladb_free_string. Always frees the job.
|
|
228
|
+
*/
|
|
229
|
+
char *taladb_job_take_result(TalaDbJob *job);
|
|
230
|
+
|
|
231
|
+
/** Detach the job and free the handle without waiting. */
|
|
232
|
+
void taladb_job_cancel(TalaDbJob *job);
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Last error message for the current thread, or NULL.
|
|
236
|
+
* The returned pointer is valid until the next taladb_* call on this thread.
|
|
237
|
+
* Do NOT free.
|
|
238
|
+
*/
|
|
239
|
+
const char *taladb_last_error(void);
|
|
240
|
+
|
|
147
241
|
#ifdef __cplusplus
|
|
148
242
|
} /* extern "C" */
|
|
149
243
|
#endif
|
package/ios/TalaDB.mm
CHANGED
|
@@ -149,4 +149,39 @@ RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(void, dropIndex:(NSString *)collection field
|
|
|
149
149
|
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(void, createFtsIndex:(NSString *)collection field:(NSString *)field) {}
|
|
150
150
|
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(void, dropFtsIndex:(NSString *)collection field:(NSString *)field) {}
|
|
151
151
|
|
|
152
|
+
// Vector index + findNearest — JSI-only; stubs exist to satisfy TurboModule Codegen.
|
|
153
|
+
|
|
154
|
+
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(void, createVectorIndex:(NSString *)collection
|
|
155
|
+
field:(NSString *)field
|
|
156
|
+
dimensions:(double)dimensions
|
|
157
|
+
opts:(NSDictionary *)opts) {}
|
|
158
|
+
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(void, dropVectorIndex:(NSString *)collection field:(NSString *)field) {}
|
|
159
|
+
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(void, upgradeVectorIndex:(NSString *)collection field:(NSString *)field) {}
|
|
160
|
+
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSArray *, findNearest:(NSString *)collection
|
|
161
|
+
field:(NSString *)field
|
|
162
|
+
query:(NSArray *)query
|
|
163
|
+
topK:(double)topK
|
|
164
|
+
filter:(NSDictionary *)filter) {
|
|
165
|
+
return @[];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Async variants use the standard Promise bridge — JSI-only at runtime.
|
|
169
|
+
|
|
170
|
+
RCT_EXPORT_METHOD(findNearestAsync:(NSString *)collection
|
|
171
|
+
field:(NSString *)field
|
|
172
|
+
query:(NSArray *)query
|
|
173
|
+
topK:(double)topK
|
|
174
|
+
filter:(NSDictionary *)filter
|
|
175
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
176
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
177
|
+
resolve(@[]);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
RCT_EXPORT_METHOD(findAsync:(NSString *)collection
|
|
181
|
+
filter:(NSDictionary *)filter
|
|
182
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
183
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
184
|
+
resolve(@[]);
|
|
185
|
+
}
|
|
186
|
+
|
|
152
187
|
@end
|
|
@@ -6,38 +6,38 @@
|
|
|
6
6
|
<array>
|
|
7
7
|
<dict>
|
|
8
8
|
<key>BinaryPath</key>
|
|
9
|
-
<string>
|
|
9
|
+
<string>libtaladb_ffi.a</string>
|
|
10
10
|
<key>HeadersPath</key>
|
|
11
11
|
<string>Headers</string>
|
|
12
12
|
<key>LibraryIdentifier</key>
|
|
13
|
-
<string>ios-
|
|
13
|
+
<string>ios-arm64</string>
|
|
14
14
|
<key>LibraryPath</key>
|
|
15
|
-
<string>
|
|
15
|
+
<string>libtaladb_ffi.a</string>
|
|
16
16
|
<key>SupportedArchitectures</key>
|
|
17
17
|
<array>
|
|
18
18
|
<string>arm64</string>
|
|
19
|
-
<string>x86_64</string>
|
|
20
19
|
</array>
|
|
21
20
|
<key>SupportedPlatform</key>
|
|
22
21
|
<string>ios</string>
|
|
23
|
-
<key>SupportedPlatformVariant</key>
|
|
24
|
-
<string>simulator</string>
|
|
25
22
|
</dict>
|
|
26
23
|
<dict>
|
|
27
24
|
<key>BinaryPath</key>
|
|
28
|
-
<string>
|
|
25
|
+
<string>libtaladb_ffi_sim.a</string>
|
|
29
26
|
<key>HeadersPath</key>
|
|
30
27
|
<string>Headers</string>
|
|
31
28
|
<key>LibraryIdentifier</key>
|
|
32
|
-
<string>ios-
|
|
29
|
+
<string>ios-arm64_x86_64-simulator</string>
|
|
33
30
|
<key>LibraryPath</key>
|
|
34
|
-
<string>
|
|
31
|
+
<string>libtaladb_ffi_sim.a</string>
|
|
35
32
|
<key>SupportedArchitectures</key>
|
|
36
33
|
<array>
|
|
37
34
|
<string>arm64</string>
|
|
35
|
+
<string>x86_64</string>
|
|
38
36
|
</array>
|
|
39
37
|
<key>SupportedPlatform</key>
|
|
40
38
|
<string>ios</string>
|
|
39
|
+
<key>SupportedPlatformVariant</key>
|
|
40
|
+
<string>simulator</string>
|
|
41
41
|
</dict>
|
|
42
42
|
</array>
|
|
43
43
|
<key>CFBundlePackageType</key>
|