@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.
Files changed (24) hide show
  1. package/android/src/main/java/com/taladb/TalaDBModule.kt +33 -0
  2. package/android/src/main/jniLibs/arm64-v8a/libredb-0944877d0a949895.so +0 -0
  3. package/android/src/main/jniLibs/arm64-v8a/libtaladb_ffi.so +0 -0
  4. package/android/src/main/jniLibs/armeabi-v7a/{libredb-6543807032a4d3d4.so → libredb-3eb5d788992b33b3.so} +0 -0
  5. package/android/src/main/jniLibs/armeabi-v7a/libtaladb_ffi.so +0 -0
  6. package/android/src/main/jniLibs/x86_64/libredb-ece86615cd326687.so +0 -0
  7. package/android/src/main/jniLibs/x86_64/libtaladb_ffi.so +0 -0
  8. package/cpp/TalaDBHostObject.cpp +340 -0
  9. package/cpp/TalaDBHostObject.h +17 -0
  10. package/cpp/taladb.h +94 -0
  11. package/ios/TalaDB.mm +35 -0
  12. package/ios/TalaDBFfi.xcframework/Info.plist +9 -9
  13. package/ios/TalaDBFfi.xcframework/ios-arm64/Headers/TalaDBHostObject.cpp +340 -0
  14. package/ios/TalaDBFfi.xcframework/ios-arm64/Headers/TalaDBHostObject.h +17 -0
  15. package/ios/TalaDBFfi.xcframework/ios-arm64/Headers/taladb.h +94 -0
  16. package/ios/TalaDBFfi.xcframework/ios-arm64/libtaladb_ffi.a +0 -0
  17. package/ios/TalaDBFfi.xcframework/ios-arm64_x86_64-simulator/Headers/TalaDBHostObject.cpp +340 -0
  18. package/ios/TalaDBFfi.xcframework/ios-arm64_x86_64-simulator/Headers/TalaDBHostObject.h +17 -0
  19. package/ios/TalaDBFfi.xcframework/ios-arm64_x86_64-simulator/Headers/taladb.h +94 -0
  20. package/ios/TalaDBFfi.xcframework/ios-arm64_x86_64-simulator/libtaladb_ffi_sim.a +0 -0
  21. package/package.json +1 -1
  22. package/src/NativeTalaDB.ts +45 -0
  23. package/android/src/main/jniLibs/arm64-v8a/libredb-5d2d0997ccb02a9b.so +0 -0
  24. 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
  }
@@ -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
  // ------------------------------------------------------------------
@@ -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>libtaladb_ffi_sim.a</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-arm64_x86_64-simulator</string>
13
+ <string>ios-arm64</string>
14
14
  <key>LibraryPath</key>
15
- <string>libtaladb_ffi_sim.a</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>libtaladb_ffi.a</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-arm64</string>
29
+ <string>ios-arm64_x86_64-simulator</string>
33
30
  <key>LibraryPath</key>
34
- <string>libtaladb_ffi.a</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>