@taladb/react-native 0.11.1 → 0.11.3
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/jniLibs/arm64-v8a/libredb-86d97267aa016807.so +0 -0
- package/android/src/main/jniLibs/arm64-v8a/libtaladb_ffi.so +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/{libredb-9ad37c880f82971f.so → libredb-1517f4c9245410e5.so} +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/libtaladb_ffi.so +0 -0
- package/android/src/main/jniLibs/x86_64/libredb-6d972d719c632f03.so +0 -0
- package/android/src/main/jniLibs/x86_64/libtaladb_ffi.so +0 -0
- package/cpp/TalaDBHostObject.cpp +52 -19
- package/cpp/TalaDBHostObject.h +4 -4
- package/cpp/taladb.h +2 -0
- package/ios/TalaDBFfi.xcframework/Info.plist +9 -9
- package/ios/TalaDBFfi.xcframework/ios-arm64/Headers/TalaDBHostObject.cpp +52 -19
- package/ios/TalaDBFfi.xcframework/ios-arm64/Headers/TalaDBHostObject.h +4 -4
- package/ios/TalaDBFfi.xcframework/ios-arm64/Headers/taladb.h +2 -0
- package/ios/TalaDBFfi.xcframework/ios-arm64/libtaladb_ffi.a +0 -0
- package/ios/TalaDBFfi.xcframework/ios-arm64_x86_64-simulator/Headers/TalaDBHostObject.cpp +52 -19
- package/ios/TalaDBFfi.xcframework/ios-arm64_x86_64-simulator/Headers/TalaDBHostObject.h +4 -4
- package/ios/TalaDBFfi.xcframework/ios-arm64_x86_64-simulator/Headers/taladb.h +2 -0
- package/ios/TalaDBFfi.xcframework/ios-arm64_x86_64-simulator/libtaladb_ffi_sim.a +0 -0
- package/package.json +2 -2
- package/android/src/main/jniLibs/arm64-v8a/libredb-c8c61b1f6c018b08.so +0 -0
- package/android/src/main/jniLibs/x86_64/libredb-5f8ad3451c0f8dea.so +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/cpp/TalaDBHostObject.cpp
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#include "TalaDBHostObject.h"
|
|
2
2
|
|
|
3
3
|
#include <memory>
|
|
4
|
+
#include <cmath>
|
|
5
|
+
#include <cstring>
|
|
6
|
+
#include <limits>
|
|
4
7
|
#include <stdexcept>
|
|
5
8
|
#include <vector>
|
|
6
9
|
|
|
@@ -101,30 +104,45 @@ void TalaDBHostObject::extractF32Query(Runtime &rt, const Value &val,
|
|
|
101
104
|
}
|
|
102
105
|
size_t n = size / sizeof(float);
|
|
103
106
|
out.resize(n);
|
|
104
|
-
std::memcpy(out.data(), ab.data(rt), size);
|
|
107
|
+
if (size != 0) std::memcpy(out.data(), ab.data(rt), size);
|
|
105
108
|
return;
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
// Float32Array exposes `.buffer`, `.byteOffset`, `.byteLength`. We read
|
|
109
112
|
// those directly and memcpy from the underlying ArrayBuffer — no JSON,
|
|
110
113
|
// no per-element Value conversion.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
auto arrayBuffer = rt.global().getPropertyAsObject(rt, "ArrayBuffer");
|
|
115
|
+
auto isView = arrayBuffer.getPropertyAsFunction(rt, "isView").call(rt, obj);
|
|
116
|
+
if (isView.isBool() && isView.getBool()) {
|
|
117
|
+
auto float32 = rt.global().getPropertyAsFunction(rt, "Float32Array");
|
|
118
|
+
if (obj.instanceOf(rt, float32)) {
|
|
114
119
|
auto buf = obj.getPropertyAsObject(rt, "buffer");
|
|
115
120
|
auto ab = buf.getArrayBuffer(rt);
|
|
116
121
|
auto offsetV = obj.getProperty(rt, "byteOffset");
|
|
117
122
|
auto lengthV = obj.getProperty(rt, "byteLength");
|
|
118
|
-
|
|
119
|
-
|
|
123
|
+
auto checkedSize = [&rt](const Value &v) -> size_t {
|
|
124
|
+
if (!v.isNumber()) throw JSError(rt, "invalid typed-array byte range");
|
|
125
|
+
double n = v.getNumber();
|
|
126
|
+
if (!std::isfinite(n) || n < 0 || std::floor(n) != n ||
|
|
127
|
+
n >= static_cast<double>(std::numeric_limits<size_t>::max())) {
|
|
128
|
+
throw JSError(rt, "invalid typed-array byte range");
|
|
129
|
+
}
|
|
130
|
+
return static_cast<size_t>(n);
|
|
131
|
+
};
|
|
132
|
+
size_t offset = checkedSize(offsetV);
|
|
133
|
+
size_t length = checkedSize(lengthV);
|
|
134
|
+
if (offset > ab.size(rt) || length > ab.size(rt) - offset) {
|
|
135
|
+
throw JSError(rt, "typed-array byte range exceeds its buffer");
|
|
136
|
+
}
|
|
120
137
|
if (length % sizeof(float) != 0) {
|
|
121
138
|
throw JSError(rt, "typed-array byteLength is not a multiple of 4");
|
|
122
139
|
}
|
|
123
140
|
size_t n = length / sizeof(float);
|
|
124
141
|
out.resize(n);
|
|
125
|
-
std::memcpy(out.data(), ab.data(rt) + offset, length);
|
|
142
|
+
if (length != 0) std::memcpy(out.data(), ab.data(rt) + offset, length);
|
|
126
143
|
return;
|
|
127
144
|
}
|
|
145
|
+
throw JSError(rt, "query typed array must be Float32Array");
|
|
128
146
|
}
|
|
129
147
|
|
|
130
148
|
// --- Fallback: number[] — per-element conversion (slow, but correct) ---
|
|
@@ -150,9 +168,8 @@ void TalaDBHostObject::extractF32Query(Runtime &rt, const Value &val,
|
|
|
150
168
|
//
|
|
151
169
|
// JSI on RN does not give us a thread-safe way to resolve a Promise from a
|
|
152
170
|
// background OS thread. We poll the job handle from the JS thread using
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
// background worker the FFI spawned.
|
|
171
|
+
// a bounded timer. The DB work itself runs on the background worker the FFI
|
|
172
|
+
// spawned; idle polling yields to rendering instead of spinning on setImmediate.
|
|
156
173
|
// ---------------------------------------------------------------------------
|
|
157
174
|
|
|
158
175
|
Value TalaDBHostObject::awaitJobAsPromise(Runtime &rt, TalaDbJob *job, bool parseAsJson) {
|
|
@@ -164,7 +181,10 @@ Value TalaDBHostObject::awaitJobAsPromise(Runtime &rt, TalaDbJob *job, bool pars
|
|
|
164
181
|
|
|
165
182
|
// The job pointer is owned by the executor closure; we null it out on
|
|
166
183
|
// consumption to prevent double-free.
|
|
167
|
-
auto jobBox = std::
|
|
184
|
+
auto jobBox = std::shared_ptr<TalaDbJob *>(new TalaDbJob *(job), [](TalaDbJob **slot) {
|
|
185
|
+
if (*slot) taladb_job_cancel(*slot);
|
|
186
|
+
delete slot;
|
|
187
|
+
});
|
|
168
188
|
|
|
169
189
|
auto executor = Function::createFromHostFunction(
|
|
170
190
|
rt, PropNameID::forAscii(rt, "taladbJobExecutor"), 2,
|
|
@@ -175,14 +195,16 @@ Value TalaDBHostObject::awaitJobAsPromise(Runtime &rt, TalaDbJob *job, bool pars
|
|
|
175
195
|
auto resolve = std::make_shared<Function>(args[0].getObject(rt).getFunction(rt));
|
|
176
196
|
auto reject = std::make_shared<Function>(args[1].getObject(rt).getFunction(rt));
|
|
177
197
|
|
|
178
|
-
// `
|
|
198
|
+
// `setTimeout` is provided by React Native. Reschedule until the
|
|
179
199
|
// worker thread finishes, then take the result on the JS thread.
|
|
180
|
-
auto
|
|
181
|
-
auto
|
|
200
|
+
auto timer = rt.global().getPropertyAsFunction(rt, "setTimeout");
|
|
201
|
+
auto timerShared = std::make_shared<Function>(std::move(timer));
|
|
182
202
|
|
|
183
|
-
//
|
|
203
|
+
// Only the pending JS tick owns the poller; the callable itself
|
|
204
|
+
// keeps a weak reference so completion releases all JSI captures.
|
|
184
205
|
auto poller = std::make_shared<std::function<void(Runtime &)>>();
|
|
185
|
-
|
|
206
|
+
std::weak_ptr<std::function<void(Runtime &)>> weakPoller = poller;
|
|
207
|
+
*poller = [jobBox, resolve, reject, timerShared, weakPoller, parseAsJson](Runtime &rt) {
|
|
186
208
|
TalaDbJob *j = *jobBox;
|
|
187
209
|
if (!j) {
|
|
188
210
|
return; // already consumed — defensive
|
|
@@ -190,13 +212,15 @@ Value TalaDBHostObject::awaitJobAsPromise(Runtime &rt, TalaDbJob *job, bool pars
|
|
|
190
212
|
int32_t state = taladb_job_poll(j);
|
|
191
213
|
if (state == 0) {
|
|
192
214
|
// Still running — reschedule.
|
|
215
|
+
auto poller = weakPoller.lock();
|
|
216
|
+
if (!poller) return;
|
|
193
217
|
auto tick = Function::createFromHostFunction(
|
|
194
218
|
rt, PropNameID::forAscii(rt, "taladbJobTick"), 0,
|
|
195
219
|
[poller](Runtime &rt, const Value &, const Value *, size_t) -> Value {
|
|
196
220
|
(*poller)(rt);
|
|
197
221
|
return Value::undefined();
|
|
198
222
|
});
|
|
199
|
-
|
|
223
|
+
timerShared->call(rt, tick, 4.0);
|
|
200
224
|
return;
|
|
201
225
|
}
|
|
202
226
|
|
|
@@ -254,7 +278,7 @@ std::vector<PropNameID> TalaDBHostObject::getPropertyNames(Runtime &rt) {
|
|
|
254
278
|
"createVectorIndex", "dropVectorIndex", "upgradeVectorIndex",
|
|
255
279
|
"findNearest", "findNearestAsync",
|
|
256
280
|
"searchText", "hybridSearch",
|
|
257
|
-
"findAsync",
|
|
281
|
+
"findAsync", "callAsync",
|
|
258
282
|
"compact",
|
|
259
283
|
"close",
|
|
260
284
|
};
|
|
@@ -275,6 +299,15 @@ void TalaDBHostObject::set(Runtime &, const PropNameID &, const Value &) {}
|
|
|
275
299
|
Value TalaDBHostObject::get(Runtime &rt, const PropNameID &propName) {
|
|
276
300
|
auto name = propName.utf8(rt);
|
|
277
301
|
|
|
302
|
+
if (name == "callAsync") {
|
|
303
|
+
return Function::createFromHostFunction(rt, PropNameID::forAscii(rt, "callAsync"), 2,
|
|
304
|
+
[this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
305
|
+
if (count < 2) throw JSError(rt, "callAsync requires an operation and argument array");
|
|
306
|
+
auto op = args[0].getString(rt).utf8(rt);
|
|
307
|
+
auto json = stringify(rt, args[1]);
|
|
308
|
+
return awaitJobAsPromise(rt, taladb_call_start(db_, op.c_str(), json.c_str()), true);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
278
311
|
// ------------------------------------------------------------------
|
|
279
312
|
// insert(collection: string, doc: object): string
|
|
280
313
|
// ------------------------------------------------------------------
|
|
@@ -642,7 +675,7 @@ Value TalaDBHostObject::get(Runtime &rt, const PropNameID &propName) {
|
|
|
642
675
|
|
|
643
676
|
// ------------------------------------------------------------------
|
|
644
677
|
// findNearestAsync(...) → Promise<{ document, score }[]>
|
|
645
|
-
// Runs on a background thread. JS thread polls via
|
|
678
|
+
// Runs on a background thread. JS thread polls via a bounded timer.
|
|
646
679
|
// ------------------------------------------------------------------
|
|
647
680
|
if (name == "findNearestAsync") {
|
|
648
681
|
return Function::createFromHostFunction(
|
package/cpp/TalaDBHostObject.h
CHANGED
|
@@ -12,9 +12,9 @@ namespace taladb {
|
|
|
12
12
|
* Installed into the JS runtime as a global:
|
|
13
13
|
* global.__TalaDB__ = <TalaDBHostObject instance>
|
|
14
14
|
*
|
|
15
|
-
* Every property access returns a JSI Function.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* Every property access returns a JSI Function. `callAsync` and vector search
|
|
16
|
+
* run Rust work on a bounded background executor and resolve a Promise from a
|
|
17
|
+
* short JS timer; legacy synchronous methods remain for binary compatibility.
|
|
18
18
|
*
|
|
19
19
|
* JSON is used at the C boundary:
|
|
20
20
|
* JS object → JSON.stringify → C string → Rust → C string → JSON.parse → JS object
|
|
@@ -62,7 +62,7 @@ private:
|
|
|
62
62
|
const facebook::jsi::Value &val,
|
|
63
63
|
std::vector<float> &out);
|
|
64
64
|
|
|
65
|
-
// Poll a TalaDbJob from the JS thread via
|
|
65
|
+
// Poll a TalaDbJob from the JS thread via a short setTimeout until done,
|
|
66
66
|
// then resolve / reject a Promise with the result / error.
|
|
67
67
|
static facebook::jsi::Value awaitJobAsPromise(facebook::jsi::Runtime &rt,
|
|
68
68
|
TalaDbJob *job,
|
package/cpp/taladb.h
CHANGED
|
@@ -289,6 +289,8 @@ int32_t taladb_job_poll(TalaDbJob *job);
|
|
|
289
289
|
char *taladb_job_take_result(TalaDbJob *job);
|
|
290
290
|
|
|
291
291
|
/** Detach the job and free the handle without waiting. */
|
|
292
|
+
TalaDbJob *taladb_call_start(TalaDbHandle *handle, const char *op, const char *args_json);
|
|
293
|
+
|
|
292
294
|
void taladb_job_cancel(TalaDbJob *job);
|
|
293
295
|
|
|
294
296
|
/**
|
|
@@ -6,38 +6,38 @@
|
|
|
6
6
|
<array>
|
|
7
7
|
<dict>
|
|
8
8
|
<key>BinaryPath</key>
|
|
9
|
-
<string>
|
|
9
|
+
<string>libtaladb_ffi_sim.a</string>
|
|
10
10
|
<key>HeadersPath</key>
|
|
11
11
|
<string>Headers</string>
|
|
12
12
|
<key>LibraryIdentifier</key>
|
|
13
|
-
<string>ios-
|
|
13
|
+
<string>ios-arm64_x86_64-simulator</string>
|
|
14
14
|
<key>LibraryPath</key>
|
|
15
|
-
<string>
|
|
15
|
+
<string>libtaladb_ffi_sim.a</string>
|
|
16
16
|
<key>SupportedArchitectures</key>
|
|
17
17
|
<array>
|
|
18
18
|
<string>arm64</string>
|
|
19
|
+
<string>x86_64</string>
|
|
19
20
|
</array>
|
|
20
21
|
<key>SupportedPlatform</key>
|
|
21
22
|
<string>ios</string>
|
|
23
|
+
<key>SupportedPlatformVariant</key>
|
|
24
|
+
<string>simulator</string>
|
|
22
25
|
</dict>
|
|
23
26
|
<dict>
|
|
24
27
|
<key>BinaryPath</key>
|
|
25
|
-
<string>
|
|
28
|
+
<string>libtaladb_ffi.a</string>
|
|
26
29
|
<key>HeadersPath</key>
|
|
27
30
|
<string>Headers</string>
|
|
28
31
|
<key>LibraryIdentifier</key>
|
|
29
|
-
<string>ios-
|
|
32
|
+
<string>ios-arm64</string>
|
|
30
33
|
<key>LibraryPath</key>
|
|
31
|
-
<string>
|
|
34
|
+
<string>libtaladb_ffi.a</string>
|
|
32
35
|
<key>SupportedArchitectures</key>
|
|
33
36
|
<array>
|
|
34
37
|
<string>arm64</string>
|
|
35
|
-
<string>x86_64</string>
|
|
36
38
|
</array>
|
|
37
39
|
<key>SupportedPlatform</key>
|
|
38
40
|
<string>ios</string>
|
|
39
|
-
<key>SupportedPlatformVariant</key>
|
|
40
|
-
<string>simulator</string>
|
|
41
41
|
</dict>
|
|
42
42
|
</array>
|
|
43
43
|
<key>CFBundlePackageType</key>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#include "TalaDBHostObject.h"
|
|
2
2
|
|
|
3
3
|
#include <memory>
|
|
4
|
+
#include <cmath>
|
|
5
|
+
#include <cstring>
|
|
6
|
+
#include <limits>
|
|
4
7
|
#include <stdexcept>
|
|
5
8
|
#include <vector>
|
|
6
9
|
|
|
@@ -101,30 +104,45 @@ void TalaDBHostObject::extractF32Query(Runtime &rt, const Value &val,
|
|
|
101
104
|
}
|
|
102
105
|
size_t n = size / sizeof(float);
|
|
103
106
|
out.resize(n);
|
|
104
|
-
std::memcpy(out.data(), ab.data(rt), size);
|
|
107
|
+
if (size != 0) std::memcpy(out.data(), ab.data(rt), size);
|
|
105
108
|
return;
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
// Float32Array exposes `.buffer`, `.byteOffset`, `.byteLength`. We read
|
|
109
112
|
// those directly and memcpy from the underlying ArrayBuffer — no JSON,
|
|
110
113
|
// no per-element Value conversion.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
auto arrayBuffer = rt.global().getPropertyAsObject(rt, "ArrayBuffer");
|
|
115
|
+
auto isView = arrayBuffer.getPropertyAsFunction(rt, "isView").call(rt, obj);
|
|
116
|
+
if (isView.isBool() && isView.getBool()) {
|
|
117
|
+
auto float32 = rt.global().getPropertyAsFunction(rt, "Float32Array");
|
|
118
|
+
if (obj.instanceOf(rt, float32)) {
|
|
114
119
|
auto buf = obj.getPropertyAsObject(rt, "buffer");
|
|
115
120
|
auto ab = buf.getArrayBuffer(rt);
|
|
116
121
|
auto offsetV = obj.getProperty(rt, "byteOffset");
|
|
117
122
|
auto lengthV = obj.getProperty(rt, "byteLength");
|
|
118
|
-
|
|
119
|
-
|
|
123
|
+
auto checkedSize = [&rt](const Value &v) -> size_t {
|
|
124
|
+
if (!v.isNumber()) throw JSError(rt, "invalid typed-array byte range");
|
|
125
|
+
double n = v.getNumber();
|
|
126
|
+
if (!std::isfinite(n) || n < 0 || std::floor(n) != n ||
|
|
127
|
+
n >= static_cast<double>(std::numeric_limits<size_t>::max())) {
|
|
128
|
+
throw JSError(rt, "invalid typed-array byte range");
|
|
129
|
+
}
|
|
130
|
+
return static_cast<size_t>(n);
|
|
131
|
+
};
|
|
132
|
+
size_t offset = checkedSize(offsetV);
|
|
133
|
+
size_t length = checkedSize(lengthV);
|
|
134
|
+
if (offset > ab.size(rt) || length > ab.size(rt) - offset) {
|
|
135
|
+
throw JSError(rt, "typed-array byte range exceeds its buffer");
|
|
136
|
+
}
|
|
120
137
|
if (length % sizeof(float) != 0) {
|
|
121
138
|
throw JSError(rt, "typed-array byteLength is not a multiple of 4");
|
|
122
139
|
}
|
|
123
140
|
size_t n = length / sizeof(float);
|
|
124
141
|
out.resize(n);
|
|
125
|
-
std::memcpy(out.data(), ab.data(rt) + offset, length);
|
|
142
|
+
if (length != 0) std::memcpy(out.data(), ab.data(rt) + offset, length);
|
|
126
143
|
return;
|
|
127
144
|
}
|
|
145
|
+
throw JSError(rt, "query typed array must be Float32Array");
|
|
128
146
|
}
|
|
129
147
|
|
|
130
148
|
// --- Fallback: number[] — per-element conversion (slow, but correct) ---
|
|
@@ -150,9 +168,8 @@ void TalaDBHostObject::extractF32Query(Runtime &rt, const Value &val,
|
|
|
150
168
|
//
|
|
151
169
|
// JSI on RN does not give us a thread-safe way to resolve a Promise from a
|
|
152
170
|
// background OS thread. We poll the job handle from the JS thread using
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
// background worker the FFI spawned.
|
|
171
|
+
// a bounded timer. The DB work itself runs on the background worker the FFI
|
|
172
|
+
// spawned; idle polling yields to rendering instead of spinning on setImmediate.
|
|
156
173
|
// ---------------------------------------------------------------------------
|
|
157
174
|
|
|
158
175
|
Value TalaDBHostObject::awaitJobAsPromise(Runtime &rt, TalaDbJob *job, bool parseAsJson) {
|
|
@@ -164,7 +181,10 @@ Value TalaDBHostObject::awaitJobAsPromise(Runtime &rt, TalaDbJob *job, bool pars
|
|
|
164
181
|
|
|
165
182
|
// The job pointer is owned by the executor closure; we null it out on
|
|
166
183
|
// consumption to prevent double-free.
|
|
167
|
-
auto jobBox = std::
|
|
184
|
+
auto jobBox = std::shared_ptr<TalaDbJob *>(new TalaDbJob *(job), [](TalaDbJob **slot) {
|
|
185
|
+
if (*slot) taladb_job_cancel(*slot);
|
|
186
|
+
delete slot;
|
|
187
|
+
});
|
|
168
188
|
|
|
169
189
|
auto executor = Function::createFromHostFunction(
|
|
170
190
|
rt, PropNameID::forAscii(rt, "taladbJobExecutor"), 2,
|
|
@@ -175,14 +195,16 @@ Value TalaDBHostObject::awaitJobAsPromise(Runtime &rt, TalaDbJob *job, bool pars
|
|
|
175
195
|
auto resolve = std::make_shared<Function>(args[0].getObject(rt).getFunction(rt));
|
|
176
196
|
auto reject = std::make_shared<Function>(args[1].getObject(rt).getFunction(rt));
|
|
177
197
|
|
|
178
|
-
// `
|
|
198
|
+
// `setTimeout` is provided by React Native. Reschedule until the
|
|
179
199
|
// worker thread finishes, then take the result on the JS thread.
|
|
180
|
-
auto
|
|
181
|
-
auto
|
|
200
|
+
auto timer = rt.global().getPropertyAsFunction(rt, "setTimeout");
|
|
201
|
+
auto timerShared = std::make_shared<Function>(std::move(timer));
|
|
182
202
|
|
|
183
|
-
//
|
|
203
|
+
// Only the pending JS tick owns the poller; the callable itself
|
|
204
|
+
// keeps a weak reference so completion releases all JSI captures.
|
|
184
205
|
auto poller = std::make_shared<std::function<void(Runtime &)>>();
|
|
185
|
-
|
|
206
|
+
std::weak_ptr<std::function<void(Runtime &)>> weakPoller = poller;
|
|
207
|
+
*poller = [jobBox, resolve, reject, timerShared, weakPoller, parseAsJson](Runtime &rt) {
|
|
186
208
|
TalaDbJob *j = *jobBox;
|
|
187
209
|
if (!j) {
|
|
188
210
|
return; // already consumed — defensive
|
|
@@ -190,13 +212,15 @@ Value TalaDBHostObject::awaitJobAsPromise(Runtime &rt, TalaDbJob *job, bool pars
|
|
|
190
212
|
int32_t state = taladb_job_poll(j);
|
|
191
213
|
if (state == 0) {
|
|
192
214
|
// Still running — reschedule.
|
|
215
|
+
auto poller = weakPoller.lock();
|
|
216
|
+
if (!poller) return;
|
|
193
217
|
auto tick = Function::createFromHostFunction(
|
|
194
218
|
rt, PropNameID::forAscii(rt, "taladbJobTick"), 0,
|
|
195
219
|
[poller](Runtime &rt, const Value &, const Value *, size_t) -> Value {
|
|
196
220
|
(*poller)(rt);
|
|
197
221
|
return Value::undefined();
|
|
198
222
|
});
|
|
199
|
-
|
|
223
|
+
timerShared->call(rt, tick, 4.0);
|
|
200
224
|
return;
|
|
201
225
|
}
|
|
202
226
|
|
|
@@ -254,7 +278,7 @@ std::vector<PropNameID> TalaDBHostObject::getPropertyNames(Runtime &rt) {
|
|
|
254
278
|
"createVectorIndex", "dropVectorIndex", "upgradeVectorIndex",
|
|
255
279
|
"findNearest", "findNearestAsync",
|
|
256
280
|
"searchText", "hybridSearch",
|
|
257
|
-
"findAsync",
|
|
281
|
+
"findAsync", "callAsync",
|
|
258
282
|
"compact",
|
|
259
283
|
"close",
|
|
260
284
|
};
|
|
@@ -275,6 +299,15 @@ void TalaDBHostObject::set(Runtime &, const PropNameID &, const Value &) {}
|
|
|
275
299
|
Value TalaDBHostObject::get(Runtime &rt, const PropNameID &propName) {
|
|
276
300
|
auto name = propName.utf8(rt);
|
|
277
301
|
|
|
302
|
+
if (name == "callAsync") {
|
|
303
|
+
return Function::createFromHostFunction(rt, PropNameID::forAscii(rt, "callAsync"), 2,
|
|
304
|
+
[this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
305
|
+
if (count < 2) throw JSError(rt, "callAsync requires an operation and argument array");
|
|
306
|
+
auto op = args[0].getString(rt).utf8(rt);
|
|
307
|
+
auto json = stringify(rt, args[1]);
|
|
308
|
+
return awaitJobAsPromise(rt, taladb_call_start(db_, op.c_str(), json.c_str()), true);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
278
311
|
// ------------------------------------------------------------------
|
|
279
312
|
// insert(collection: string, doc: object): string
|
|
280
313
|
// ------------------------------------------------------------------
|
|
@@ -642,7 +675,7 @@ Value TalaDBHostObject::get(Runtime &rt, const PropNameID &propName) {
|
|
|
642
675
|
|
|
643
676
|
// ------------------------------------------------------------------
|
|
644
677
|
// findNearestAsync(...) → Promise<{ document, score }[]>
|
|
645
|
-
// Runs on a background thread. JS thread polls via
|
|
678
|
+
// Runs on a background thread. JS thread polls via a bounded timer.
|
|
646
679
|
// ------------------------------------------------------------------
|
|
647
680
|
if (name == "findNearestAsync") {
|
|
648
681
|
return Function::createFromHostFunction(
|
|
@@ -12,9 +12,9 @@ namespace taladb {
|
|
|
12
12
|
* Installed into the JS runtime as a global:
|
|
13
13
|
* global.__TalaDB__ = <TalaDBHostObject instance>
|
|
14
14
|
*
|
|
15
|
-
* Every property access returns a JSI Function.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* Every property access returns a JSI Function. `callAsync` and vector search
|
|
16
|
+
* run Rust work on a bounded background executor and resolve a Promise from a
|
|
17
|
+
* short JS timer; legacy synchronous methods remain for binary compatibility.
|
|
18
18
|
*
|
|
19
19
|
* JSON is used at the C boundary:
|
|
20
20
|
* JS object → JSON.stringify → C string → Rust → C string → JSON.parse → JS object
|
|
@@ -62,7 +62,7 @@ private:
|
|
|
62
62
|
const facebook::jsi::Value &val,
|
|
63
63
|
std::vector<float> &out);
|
|
64
64
|
|
|
65
|
-
// Poll a TalaDbJob from the JS thread via
|
|
65
|
+
// Poll a TalaDbJob from the JS thread via a short setTimeout until done,
|
|
66
66
|
// then resolve / reject a Promise with the result / error.
|
|
67
67
|
static facebook::jsi::Value awaitJobAsPromise(facebook::jsi::Runtime &rt,
|
|
68
68
|
TalaDbJob *job,
|
|
@@ -289,6 +289,8 @@ int32_t taladb_job_poll(TalaDbJob *job);
|
|
|
289
289
|
char *taladb_job_take_result(TalaDbJob *job);
|
|
290
290
|
|
|
291
291
|
/** Detach the job and free the handle without waiting. */
|
|
292
|
+
TalaDbJob *taladb_call_start(TalaDbHandle *handle, const char *op, const char *args_json);
|
|
293
|
+
|
|
292
294
|
void taladb_job_cancel(TalaDbJob *job);
|
|
293
295
|
|
|
294
296
|
/**
|
|
Binary file
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#include "TalaDBHostObject.h"
|
|
2
2
|
|
|
3
3
|
#include <memory>
|
|
4
|
+
#include <cmath>
|
|
5
|
+
#include <cstring>
|
|
6
|
+
#include <limits>
|
|
4
7
|
#include <stdexcept>
|
|
5
8
|
#include <vector>
|
|
6
9
|
|
|
@@ -101,30 +104,45 @@ void TalaDBHostObject::extractF32Query(Runtime &rt, const Value &val,
|
|
|
101
104
|
}
|
|
102
105
|
size_t n = size / sizeof(float);
|
|
103
106
|
out.resize(n);
|
|
104
|
-
std::memcpy(out.data(), ab.data(rt), size);
|
|
107
|
+
if (size != 0) std::memcpy(out.data(), ab.data(rt), size);
|
|
105
108
|
return;
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
// Float32Array exposes `.buffer`, `.byteOffset`, `.byteLength`. We read
|
|
109
112
|
// those directly and memcpy from the underlying ArrayBuffer — no JSON,
|
|
110
113
|
// no per-element Value conversion.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
auto arrayBuffer = rt.global().getPropertyAsObject(rt, "ArrayBuffer");
|
|
115
|
+
auto isView = arrayBuffer.getPropertyAsFunction(rt, "isView").call(rt, obj);
|
|
116
|
+
if (isView.isBool() && isView.getBool()) {
|
|
117
|
+
auto float32 = rt.global().getPropertyAsFunction(rt, "Float32Array");
|
|
118
|
+
if (obj.instanceOf(rt, float32)) {
|
|
114
119
|
auto buf = obj.getPropertyAsObject(rt, "buffer");
|
|
115
120
|
auto ab = buf.getArrayBuffer(rt);
|
|
116
121
|
auto offsetV = obj.getProperty(rt, "byteOffset");
|
|
117
122
|
auto lengthV = obj.getProperty(rt, "byteLength");
|
|
118
|
-
|
|
119
|
-
|
|
123
|
+
auto checkedSize = [&rt](const Value &v) -> size_t {
|
|
124
|
+
if (!v.isNumber()) throw JSError(rt, "invalid typed-array byte range");
|
|
125
|
+
double n = v.getNumber();
|
|
126
|
+
if (!std::isfinite(n) || n < 0 || std::floor(n) != n ||
|
|
127
|
+
n >= static_cast<double>(std::numeric_limits<size_t>::max())) {
|
|
128
|
+
throw JSError(rt, "invalid typed-array byte range");
|
|
129
|
+
}
|
|
130
|
+
return static_cast<size_t>(n);
|
|
131
|
+
};
|
|
132
|
+
size_t offset = checkedSize(offsetV);
|
|
133
|
+
size_t length = checkedSize(lengthV);
|
|
134
|
+
if (offset > ab.size(rt) || length > ab.size(rt) - offset) {
|
|
135
|
+
throw JSError(rt, "typed-array byte range exceeds its buffer");
|
|
136
|
+
}
|
|
120
137
|
if (length % sizeof(float) != 0) {
|
|
121
138
|
throw JSError(rt, "typed-array byteLength is not a multiple of 4");
|
|
122
139
|
}
|
|
123
140
|
size_t n = length / sizeof(float);
|
|
124
141
|
out.resize(n);
|
|
125
|
-
std::memcpy(out.data(), ab.data(rt) + offset, length);
|
|
142
|
+
if (length != 0) std::memcpy(out.data(), ab.data(rt) + offset, length);
|
|
126
143
|
return;
|
|
127
144
|
}
|
|
145
|
+
throw JSError(rt, "query typed array must be Float32Array");
|
|
128
146
|
}
|
|
129
147
|
|
|
130
148
|
// --- Fallback: number[] — per-element conversion (slow, but correct) ---
|
|
@@ -150,9 +168,8 @@ void TalaDBHostObject::extractF32Query(Runtime &rt, const Value &val,
|
|
|
150
168
|
//
|
|
151
169
|
// JSI on RN does not give us a thread-safe way to resolve a Promise from a
|
|
152
170
|
// background OS thread. We poll the job handle from the JS thread using
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
// background worker the FFI spawned.
|
|
171
|
+
// a bounded timer. The DB work itself runs on the background worker the FFI
|
|
172
|
+
// spawned; idle polling yields to rendering instead of spinning on setImmediate.
|
|
156
173
|
// ---------------------------------------------------------------------------
|
|
157
174
|
|
|
158
175
|
Value TalaDBHostObject::awaitJobAsPromise(Runtime &rt, TalaDbJob *job, bool parseAsJson) {
|
|
@@ -164,7 +181,10 @@ Value TalaDBHostObject::awaitJobAsPromise(Runtime &rt, TalaDbJob *job, bool pars
|
|
|
164
181
|
|
|
165
182
|
// The job pointer is owned by the executor closure; we null it out on
|
|
166
183
|
// consumption to prevent double-free.
|
|
167
|
-
auto jobBox = std::
|
|
184
|
+
auto jobBox = std::shared_ptr<TalaDbJob *>(new TalaDbJob *(job), [](TalaDbJob **slot) {
|
|
185
|
+
if (*slot) taladb_job_cancel(*slot);
|
|
186
|
+
delete slot;
|
|
187
|
+
});
|
|
168
188
|
|
|
169
189
|
auto executor = Function::createFromHostFunction(
|
|
170
190
|
rt, PropNameID::forAscii(rt, "taladbJobExecutor"), 2,
|
|
@@ -175,14 +195,16 @@ Value TalaDBHostObject::awaitJobAsPromise(Runtime &rt, TalaDbJob *job, bool pars
|
|
|
175
195
|
auto resolve = std::make_shared<Function>(args[0].getObject(rt).getFunction(rt));
|
|
176
196
|
auto reject = std::make_shared<Function>(args[1].getObject(rt).getFunction(rt));
|
|
177
197
|
|
|
178
|
-
// `
|
|
198
|
+
// `setTimeout` is provided by React Native. Reschedule until the
|
|
179
199
|
// worker thread finishes, then take the result on the JS thread.
|
|
180
|
-
auto
|
|
181
|
-
auto
|
|
200
|
+
auto timer = rt.global().getPropertyAsFunction(rt, "setTimeout");
|
|
201
|
+
auto timerShared = std::make_shared<Function>(std::move(timer));
|
|
182
202
|
|
|
183
|
-
//
|
|
203
|
+
// Only the pending JS tick owns the poller; the callable itself
|
|
204
|
+
// keeps a weak reference so completion releases all JSI captures.
|
|
184
205
|
auto poller = std::make_shared<std::function<void(Runtime &)>>();
|
|
185
|
-
|
|
206
|
+
std::weak_ptr<std::function<void(Runtime &)>> weakPoller = poller;
|
|
207
|
+
*poller = [jobBox, resolve, reject, timerShared, weakPoller, parseAsJson](Runtime &rt) {
|
|
186
208
|
TalaDbJob *j = *jobBox;
|
|
187
209
|
if (!j) {
|
|
188
210
|
return; // already consumed — defensive
|
|
@@ -190,13 +212,15 @@ Value TalaDBHostObject::awaitJobAsPromise(Runtime &rt, TalaDbJob *job, bool pars
|
|
|
190
212
|
int32_t state = taladb_job_poll(j);
|
|
191
213
|
if (state == 0) {
|
|
192
214
|
// Still running — reschedule.
|
|
215
|
+
auto poller = weakPoller.lock();
|
|
216
|
+
if (!poller) return;
|
|
193
217
|
auto tick = Function::createFromHostFunction(
|
|
194
218
|
rt, PropNameID::forAscii(rt, "taladbJobTick"), 0,
|
|
195
219
|
[poller](Runtime &rt, const Value &, const Value *, size_t) -> Value {
|
|
196
220
|
(*poller)(rt);
|
|
197
221
|
return Value::undefined();
|
|
198
222
|
});
|
|
199
|
-
|
|
223
|
+
timerShared->call(rt, tick, 4.0);
|
|
200
224
|
return;
|
|
201
225
|
}
|
|
202
226
|
|
|
@@ -254,7 +278,7 @@ std::vector<PropNameID> TalaDBHostObject::getPropertyNames(Runtime &rt) {
|
|
|
254
278
|
"createVectorIndex", "dropVectorIndex", "upgradeVectorIndex",
|
|
255
279
|
"findNearest", "findNearestAsync",
|
|
256
280
|
"searchText", "hybridSearch",
|
|
257
|
-
"findAsync",
|
|
281
|
+
"findAsync", "callAsync",
|
|
258
282
|
"compact",
|
|
259
283
|
"close",
|
|
260
284
|
};
|
|
@@ -275,6 +299,15 @@ void TalaDBHostObject::set(Runtime &, const PropNameID &, const Value &) {}
|
|
|
275
299
|
Value TalaDBHostObject::get(Runtime &rt, const PropNameID &propName) {
|
|
276
300
|
auto name = propName.utf8(rt);
|
|
277
301
|
|
|
302
|
+
if (name == "callAsync") {
|
|
303
|
+
return Function::createFromHostFunction(rt, PropNameID::forAscii(rt, "callAsync"), 2,
|
|
304
|
+
[this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
305
|
+
if (count < 2) throw JSError(rt, "callAsync requires an operation and argument array");
|
|
306
|
+
auto op = args[0].getString(rt).utf8(rt);
|
|
307
|
+
auto json = stringify(rt, args[1]);
|
|
308
|
+
return awaitJobAsPromise(rt, taladb_call_start(db_, op.c_str(), json.c_str()), true);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
278
311
|
// ------------------------------------------------------------------
|
|
279
312
|
// insert(collection: string, doc: object): string
|
|
280
313
|
// ------------------------------------------------------------------
|
|
@@ -642,7 +675,7 @@ Value TalaDBHostObject::get(Runtime &rt, const PropNameID &propName) {
|
|
|
642
675
|
|
|
643
676
|
// ------------------------------------------------------------------
|
|
644
677
|
// findNearestAsync(...) → Promise<{ document, score }[]>
|
|
645
|
-
// Runs on a background thread. JS thread polls via
|
|
678
|
+
// Runs on a background thread. JS thread polls via a bounded timer.
|
|
646
679
|
// ------------------------------------------------------------------
|
|
647
680
|
if (name == "findNearestAsync") {
|
|
648
681
|
return Function::createFromHostFunction(
|
|
@@ -12,9 +12,9 @@ namespace taladb {
|
|
|
12
12
|
* Installed into the JS runtime as a global:
|
|
13
13
|
* global.__TalaDB__ = <TalaDBHostObject instance>
|
|
14
14
|
*
|
|
15
|
-
* Every property access returns a JSI Function.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* Every property access returns a JSI Function. `callAsync` and vector search
|
|
16
|
+
* run Rust work on a bounded background executor and resolve a Promise from a
|
|
17
|
+
* short JS timer; legacy synchronous methods remain for binary compatibility.
|
|
18
18
|
*
|
|
19
19
|
* JSON is used at the C boundary:
|
|
20
20
|
* JS object → JSON.stringify → C string → Rust → C string → JSON.parse → JS object
|
|
@@ -62,7 +62,7 @@ private:
|
|
|
62
62
|
const facebook::jsi::Value &val,
|
|
63
63
|
std::vector<float> &out);
|
|
64
64
|
|
|
65
|
-
// Poll a TalaDbJob from the JS thread via
|
|
65
|
+
// Poll a TalaDbJob from the JS thread via a short setTimeout until done,
|
|
66
66
|
// then resolve / reject a Promise with the result / error.
|
|
67
67
|
static facebook::jsi::Value awaitJobAsPromise(facebook::jsi::Runtime &rt,
|
|
68
68
|
TalaDbJob *job,
|
|
@@ -289,6 +289,8 @@ int32_t taladb_job_poll(TalaDbJob *job);
|
|
|
289
289
|
char *taladb_job_take_result(TalaDbJob *job);
|
|
290
290
|
|
|
291
291
|
/** Detach the job and free the handle without waiting. */
|
|
292
|
+
TalaDbJob *taladb_call_start(TalaDbHandle *handle, const char *op, const char *args_json);
|
|
293
|
+
|
|
292
294
|
void taladb_job_cancel(TalaDbJob *job);
|
|
293
295
|
|
|
294
296
|
/**
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taladb/react-native",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.3",
|
|
4
4
|
"description": "TalaDB React Native module — document and vector database via JSI HostObject",
|
|
5
5
|
"main": "src/index",
|
|
6
6
|
"types": "src/index.tsx",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"react-native": "^0.83.0"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"taladb": "0.11.
|
|
52
|
+
"taladb": "0.11.3"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"typecheck": "tsc --noEmit",
|
|
Binary file
|
|
Binary file
|