@taladb/react-native 0.8.4 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/taladb/TalaDBModule.kt +24 -9
- package/android/src/main/jniLibs/arm64-v8a/libtaladb_ffi.so +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/libtaladb_ffi.so +0 -0
- package/android/src/main/jniLibs/x86_64/libtaladb_ffi.so +0 -0
- package/cpp/TalaDBHostObject.cpp +88 -0
- package/cpp/TalaDBJni.cpp +16 -5
- package/cpp/taladb.h +34 -5
- package/ios/TalaDB.mm +56 -28
- package/ios/TalaDBFfi.xcframework/Info.plist +9 -9
- package/ios/TalaDBFfi.xcframework/ios-arm64/Headers/TalaDBHostObject.cpp +88 -0
- package/ios/TalaDBFfi.xcframework/ios-arm64/Headers/TalaDBJni.cpp +16 -5
- package/ios/TalaDBFfi.xcframework/ios-arm64/Headers/taladb.h +34 -5
- package/ios/TalaDBFfi.xcframework/ios-arm64/libtaladb_ffi.a +0 -0
- package/ios/TalaDBFfi.xcframework/ios-arm64_x86_64-simulator/Headers/TalaDBHostObject.cpp +88 -0
- package/ios/TalaDBFfi.xcframework/ios-arm64_x86_64-simulator/Headers/TalaDBJni.cpp +16 -5
- package/ios/TalaDBFfi.xcframework/ios-arm64_x86_64-simulator/Headers/taladb.h +34 -5
- package/ios/TalaDBFfi.xcframework/ios-arm64_x86_64-simulator/libtaladb_ffi_sim.a +0 -0
- package/package.json +1 -1
- package/src/index.tsx +45 -13
|
@@ -51,7 +51,8 @@ class TalaDBModule(private val reactContext: ReactApplicationContext) :
|
|
|
51
51
|
* JSI runtime identified by [jsContextNativePtr].
|
|
52
52
|
* Called once from [initialize].
|
|
53
53
|
*/
|
|
54
|
-
private external fun nativeInstall(jsContextNativePtr: Long, dbPath: String, configJson: String?)
|
|
54
|
+
private external fun nativeInstall(jsContextNativePtr: Long, dbPath: String, configJson: String?): String?
|
|
55
|
+
private external fun nativeClose(jsContextNativePtr: Long)
|
|
55
56
|
|
|
56
57
|
// -----------------------------------------------------------------------
|
|
57
58
|
// TurboModule: initialize(dbName, configJson?) → Promise<void>
|
|
@@ -59,16 +60,33 @@ class TalaDBModule(private val reactContext: ReactApplicationContext) :
|
|
|
59
60
|
|
|
60
61
|
override fun initialize(dbName: String, configJson: String?, promise: Promise) {
|
|
61
62
|
try {
|
|
62
|
-
|
|
63
|
+
// Private app storage excluded from Android Auto Backup. The OS
|
|
64
|
+
// file-based-encryption layer protects it while the device is locked.
|
|
65
|
+
val dbFile = java.io.File(reactContext.noBackupFilesDir, dbName)
|
|
66
|
+
val legacyFile = java.io.File(reactContext.filesDir, dbName)
|
|
67
|
+
if (!dbFile.exists() && legacyFile.exists() && !legacyFile.renameTo(dbFile)) {
|
|
68
|
+
throw java.io.IOException("failed to migrate TalaDB into no-backup storage")
|
|
69
|
+
}
|
|
70
|
+
val legacySalt = java.io.File(legacyFile.absolutePath + ".taladb-salt")
|
|
71
|
+
val saltFile = java.io.File(dbFile.absolutePath + ".taladb-salt")
|
|
72
|
+
if (!saltFile.exists() && legacySalt.exists() && !legacySalt.renameTo(saltFile)) {
|
|
73
|
+
throw java.io.IOException("failed to migrate TalaDB encryption salt")
|
|
74
|
+
}
|
|
75
|
+
val dbPath = dbFile.absolutePath
|
|
63
76
|
|
|
64
77
|
// javaScriptContextHolder.get() returns the raw jsi::Runtime* pointer.
|
|
65
78
|
val jsContextPtr = reactContext.javaScriptContextHolder!!.get()
|
|
79
|
+
if (jsContextPtr == 0L) {
|
|
80
|
+
promise.reject("TALADB_NO_RUNTIME", "JSI runtime is not available")
|
|
81
|
+
return
|
|
82
|
+
}
|
|
66
83
|
|
|
67
84
|
// Install on the JS thread
|
|
68
85
|
reactContext.runOnJSQueueThread {
|
|
69
86
|
try {
|
|
70
|
-
nativeInstall(jsContextPtr, dbPath, configJson)
|
|
71
|
-
promise.resolve(null)
|
|
87
|
+
val error = nativeInstall(jsContextPtr, dbPath, configJson)
|
|
88
|
+
if (error == null) promise.resolve(null)
|
|
89
|
+
else promise.reject("TALADB_INSTALL_ERROR", error)
|
|
72
90
|
} catch (e: Exception) {
|
|
73
91
|
promise.reject("TALADB_INSTALL_ERROR", e.message, e)
|
|
74
92
|
}
|
|
@@ -89,11 +107,8 @@ class TalaDBModule(private val reactContext: ReactApplicationContext) :
|
|
|
89
107
|
try {
|
|
90
108
|
reactContext.runOnJSQueueThread {
|
|
91
109
|
try {
|
|
92
|
-
reactContext.javaScriptContextHolder?.
|
|
93
|
-
|
|
94
|
-
// HostObject destructor run (Hermes GC permitting).
|
|
95
|
-
// For an immediate close, call nativeClose() instead.
|
|
96
|
-
}
|
|
110
|
+
val ptr = reactContext.javaScriptContextHolder?.get() ?: 0L
|
|
111
|
+
if (ptr != 0L) nativeClose(ptr)
|
|
97
112
|
promise.resolve(null)
|
|
98
113
|
} catch (e: Exception) {
|
|
99
114
|
promise.reject("TALADB_CLOSE_ERROR", e.message, e)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/cpp/TalaDBHostObject.cpp
CHANGED
|
@@ -38,6 +38,7 @@ TalaDBHostObject::TalaDBHostObject(TalaDbHandle *db) : db_(db) {}
|
|
|
38
38
|
|
|
39
39
|
TalaDBHostObject::~TalaDBHostObject() {
|
|
40
40
|
if (db_) {
|
|
41
|
+
taladb_sync_flush(db_, 5000);
|
|
41
42
|
taladb_close(db_);
|
|
42
43
|
db_ = nullptr;
|
|
43
44
|
}
|
|
@@ -245,12 +246,15 @@ std::vector<PropNameID> TalaDBHostObject::getPropertyNames(Runtime &rt) {
|
|
|
245
246
|
"deleteOne", "deleteMany",
|
|
246
247
|
"count",
|
|
247
248
|
"aggregate",
|
|
249
|
+
"exportChanges", "importChanges", "listCollectionNames",
|
|
248
250
|
"createIndex", "dropIndex",
|
|
251
|
+
"createCompoundIndex", "dropCompoundIndex",
|
|
249
252
|
"createFtsIndex", "dropFtsIndex",
|
|
250
253
|
"createVectorIndex", "dropVectorIndex", "upgradeVectorIndex",
|
|
251
254
|
"findNearest", "findNearestAsync",
|
|
252
255
|
"findAsync",
|
|
253
256
|
"compact",
|
|
257
|
+
"syncStatus", "flushSync",
|
|
254
258
|
"close",
|
|
255
259
|
};
|
|
256
260
|
std::vector<PropNameID> result;
|
|
@@ -444,6 +448,51 @@ Value TalaDBHostObject::get(Runtime &rt, const PropNameID &propName) {
|
|
|
444
448
|
});
|
|
445
449
|
}
|
|
446
450
|
|
|
451
|
+
// ------------------------------------------------------------------
|
|
452
|
+
// Bidirectional sync — exportChanges / importChanges / listCollectionNames
|
|
453
|
+
// (back JS db.sync(); the runtime-agnostic loop lives in taladb/src/sync.ts)
|
|
454
|
+
// ------------------------------------------------------------------
|
|
455
|
+
if (name == "exportChanges") {
|
|
456
|
+
return Function::createFromHostFunction(
|
|
457
|
+
rt, PropNameID::forAscii(rt, "exportChanges"), 2,
|
|
458
|
+
[this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
459
|
+
if (count < 2) throw JSError(rt, "exportChanges requires 2 arguments");
|
|
460
|
+
auto collectionsJson = stringify(rt, args[0]); // string[] → JSON array
|
|
461
|
+
double sinceMs = args[1].getNumber();
|
|
462
|
+
char *result = taladb_export_changes(db_, collectionsJson.c_str(), sinceMs);
|
|
463
|
+
if (!result) throw JSError(rt, "taladb_export_changes failed");
|
|
464
|
+
std::string json(result);
|
|
465
|
+
taladb_free_string(result);
|
|
466
|
+
// Return the changeset as an opaque string (not parsed) — the
|
|
467
|
+
// JS sync adapter passes it straight to the transport.
|
|
468
|
+
return String::createFromUtf8(rt, json);
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (name == "importChanges") {
|
|
473
|
+
return Function::createFromHostFunction(
|
|
474
|
+
rt, PropNameID::forAscii(rt, "importChanges"), 1,
|
|
475
|
+
[this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
476
|
+
if (count < 1) throw JSError(rt, "importChanges requires 1 argument");
|
|
477
|
+
auto changeset = args[0].getString(rt).utf8(rt);
|
|
478
|
+
int32_t n = taladb_import_changes(db_, changeset.c_str());
|
|
479
|
+
if (n < 0) throw JSError(rt, "taladb_import_changes failed");
|
|
480
|
+
return Value(static_cast<double>(n));
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (name == "listCollectionNames") {
|
|
485
|
+
return Function::createFromHostFunction(
|
|
486
|
+
rt, PropNameID::forAscii(rt, "listCollectionNames"), 0,
|
|
487
|
+
[this](Runtime &rt, const Value &, const Value *, size_t) -> Value {
|
|
488
|
+
char *result = taladb_list_collection_names(db_);
|
|
489
|
+
if (!result) throw JSError(rt, "taladb_list_collection_names failed");
|
|
490
|
+
std::string json(result);
|
|
491
|
+
taladb_free_string(result);
|
|
492
|
+
return parse(rt, json); // JSON array → JS string[]
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
447
496
|
// ------------------------------------------------------------------
|
|
448
497
|
// createIndex / dropIndex / createFtsIndex / dropFtsIndex
|
|
449
498
|
// ------------------------------------------------------------------
|
|
@@ -463,6 +512,24 @@ Value TalaDBHostObject::get(Runtime &rt, const PropNameID &propName) {
|
|
|
463
512
|
});
|
|
464
513
|
}
|
|
465
514
|
|
|
515
|
+
// ------------------------------------------------------------------
|
|
516
|
+
// createCompoundIndex / dropCompoundIndex(collection, fields: string[]): void
|
|
517
|
+
// ------------------------------------------------------------------
|
|
518
|
+
if (name == "createCompoundIndex" || name == "dropCompoundIndex") {
|
|
519
|
+
return Function::createFromHostFunction(
|
|
520
|
+
rt, PropNameID::forUtf8(rt, name), 2,
|
|
521
|
+
[this, name](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
522
|
+
if (count < 2) throw JSError(rt, (name + " requires 2 arguments").c_str());
|
|
523
|
+
auto col = args[0].getString(rt).utf8(rt);
|
|
524
|
+
auto fieldsJson = stringify(rt, args[1]); // string[] → JSON array
|
|
525
|
+
if (name == "createCompoundIndex")
|
|
526
|
+
taladb_create_compound_index(db_, col.c_str(), fieldsJson.c_str());
|
|
527
|
+
else
|
|
528
|
+
taladb_drop_compound_index(db_, col.c_str(), fieldsJson.c_str());
|
|
529
|
+
return Value::undefined();
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
466
533
|
// ------------------------------------------------------------------
|
|
467
534
|
// createVectorIndex(collection, field, dimensions, opts?): void
|
|
468
535
|
// opts: { metric?: 'cosine'|'dot'|'euclidean', hnsw?: HnswOptions }
|
|
@@ -623,6 +690,26 @@ Value TalaDBHostObject::get(Runtime &rt, const PropNameID &propName) {
|
|
|
623
690
|
});
|
|
624
691
|
}
|
|
625
692
|
|
|
693
|
+
if (name == "syncStatus") {
|
|
694
|
+
return Function::createFromHostFunction(
|
|
695
|
+
rt, PropNameID::forAscii(rt, "syncStatus"), 0,
|
|
696
|
+
[this](Runtime &rt, const Value &, const Value *, size_t) -> Value {
|
|
697
|
+
char *raw = taladb_sync_status(db_);
|
|
698
|
+
if (!raw) throw ffiError(rt, "taladb_sync_status failed");
|
|
699
|
+
std::string json(raw); taladb_free_string(raw);
|
|
700
|
+
return parse(rt, json);
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (name == "flushSync") {
|
|
705
|
+
return Function::createFromHostFunction(
|
|
706
|
+
rt, PropNameID::forAscii(rt, "flushSync"), 1,
|
|
707
|
+
[this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
708
|
+
uint64_t timeout = count && args[0].isNumber() ? (uint64_t)args[0].getNumber() : 5000;
|
|
709
|
+
return Value(taladb_sync_flush(db_, timeout) == 1);
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
|
|
626
713
|
// ------------------------------------------------------------------
|
|
627
714
|
// close(): void (synchronous — the destructor does the real work)
|
|
628
715
|
// ------------------------------------------------------------------
|
|
@@ -631,6 +718,7 @@ Value TalaDBHostObject::get(Runtime &rt, const PropNameID &propName) {
|
|
|
631
718
|
rt, PropNameID::forAscii(rt, "close"), 0,
|
|
632
719
|
[this](Runtime &rt, const Value &, const Value *, size_t) -> Value {
|
|
633
720
|
if (db_) {
|
|
721
|
+
taladb_sync_flush(db_, 5000);
|
|
634
722
|
taladb_close(db_);
|
|
635
723
|
db_ = nullptr;
|
|
636
724
|
}
|
package/cpp/TalaDBJni.cpp
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
using namespace facebook::jsi;
|
|
23
23
|
|
|
24
|
-
extern "C" JNIEXPORT
|
|
24
|
+
extern "C" JNIEXPORT jstring JNICALL
|
|
25
25
|
Java_com_taladb_TalaDBModule_nativeInstall(
|
|
26
26
|
JNIEnv *env,
|
|
27
27
|
jobject /* thiz */,
|
|
@@ -29,6 +29,10 @@ Java_com_taladb_TalaDBModule_nativeInstall(
|
|
|
29
29
|
jstring dbPathJ,
|
|
30
30
|
jstring configJsonJ)
|
|
31
31
|
{
|
|
32
|
+
// Release any previous HostObject before reopening the same redb file.
|
|
33
|
+
auto &rt = *reinterpret_cast<Runtime *>(jsContextNativePtr);
|
|
34
|
+
rt.global().setProperty(rt, "__TalaDB__", Value::undefined());
|
|
35
|
+
|
|
32
36
|
// Resolve db path
|
|
33
37
|
const char *dbPathC = env->GetStringUTFChars(dbPathJ, nullptr);
|
|
34
38
|
std::string dbPath(dbPathC);
|
|
@@ -43,11 +47,18 @@ Java_com_taladb_TalaDBModule_nativeInstall(
|
|
|
43
47
|
} else {
|
|
44
48
|
db = taladb_open(dbPath.c_str());
|
|
45
49
|
}
|
|
46
|
-
if (!db)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
if (!db) {
|
|
51
|
+
const char *error = taladb_last_error();
|
|
52
|
+
return env->NewStringUTF(error ? error : "failed to open TalaDB");
|
|
53
|
+
}
|
|
50
54
|
|
|
51
55
|
// Install the JSI HostObject as global.__TalaDB__
|
|
52
56
|
taladb::TalaDBHostObject::install(rt, db);
|
|
57
|
+
return nullptr;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
extern "C" JNIEXPORT void JNICALL
|
|
61
|
+
Java_com_taladb_TalaDBModule_nativeClose(JNIEnv *, jobject, jlong jsContextNativePtr) {
|
|
62
|
+
auto &rt = *reinterpret_cast<Runtime *>(jsContextNativePtr);
|
|
63
|
+
rt.global().setProperty(rt, "__TalaDB__", Value::undefined());
|
|
53
64
|
}
|
package/cpp/taladb.h
CHANGED
|
@@ -154,6 +154,32 @@ char *taladb_aggregate(TalaDbHandle *handle,
|
|
|
154
154
|
const char *collection,
|
|
155
155
|
const char *pipeline_json);
|
|
156
156
|
|
|
157
|
+
/* -------------------------------------------------------------------------
|
|
158
|
+
* Bidirectional sync — changeset export / import (backs JS db.sync())
|
|
159
|
+
* ---------------------------------------------------------------------- */
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Export a changeset for the collections in collections_json (a JSON array of
|
|
163
|
+
* names) with changed_at > since_ms. Returns a JSON string, or NULL on error.
|
|
164
|
+
* Caller must free with taladb_free_string().
|
|
165
|
+
*/
|
|
166
|
+
char *taladb_export_changes(TalaDbHandle *handle,
|
|
167
|
+
const char *collections_json,
|
|
168
|
+
double since_ms);
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Merge a JSON changeset (from a remote peer) via Last-Write-Wins.
|
|
172
|
+
* Returns the number of documents changed, or -1 on error.
|
|
173
|
+
*/
|
|
174
|
+
int32_t taladb_import_changes(TalaDbHandle *handle,
|
|
175
|
+
const char *changeset_json);
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* User collection names (reserved names excluded) as a JSON array string.
|
|
179
|
+
* Returns NULL on error. Caller must free with taladb_free_string().
|
|
180
|
+
*/
|
|
181
|
+
char *taladb_list_collection_names(TalaDbHandle *handle);
|
|
182
|
+
|
|
157
183
|
/* -------------------------------------------------------------------------
|
|
158
184
|
* Index management
|
|
159
185
|
* ---------------------------------------------------------------------- */
|
|
@@ -163,6 +189,10 @@ void taladb_drop_index (TalaDbHandle *handle, const char *collection, const
|
|
|
163
189
|
void taladb_create_fts_index(TalaDbHandle *handle, const char *collection, const char *field);
|
|
164
190
|
void taladb_drop_fts_index (TalaDbHandle *handle, const char *collection, const char *field);
|
|
165
191
|
|
|
192
|
+
/* Compound (multi-field) indexes. fields_json is a JSON array of field names. */
|
|
193
|
+
void taladb_create_compound_index(TalaDbHandle *handle, const char *collection, const char *fields_json);
|
|
194
|
+
void taladb_drop_compound_index (TalaDbHandle *handle, const char *collection, const char *fields_json);
|
|
195
|
+
|
|
166
196
|
/* -------------------------------------------------------------------------
|
|
167
197
|
* Vector index management
|
|
168
198
|
*
|
|
@@ -199,6 +229,9 @@ char *taladb_find_nearest(TalaDbHandle *handle,
|
|
|
199
229
|
size_t top_k,
|
|
200
230
|
const char *filter_json);
|
|
201
231
|
|
|
232
|
+
char *taladb_sync_status(TalaDbHandle *handle);
|
|
233
|
+
int32_t taladb_sync_flush(TalaDbHandle *handle, uint64_t timeout_ms);
|
|
234
|
+
|
|
202
235
|
/* -------------------------------------------------------------------------
|
|
203
236
|
* Async job API — run heavy queries on a background thread.
|
|
204
237
|
*
|
|
@@ -208,11 +241,7 @@ char *taladb_find_nearest(TalaDbHandle *handle,
|
|
|
208
241
|
* while (taladb_job_poll(j) == 0) { } // yield to JS event loop
|
|
209
242
|
* char *json = taladb_job_take_result(j); // frees the job
|
|
210
243
|
*
|
|
211
|
-
*
|
|
212
|
-
* -----------------
|
|
213
|
-
* The handle passed to `*_start` MUST remain valid until the job has been
|
|
214
|
-
* taken (via take_result) or cancelled (via cancel). The C++ HostObject
|
|
215
|
-
* enforces this by not calling taladb_close while jobs are outstanding.
|
|
244
|
+
* Workers clone the Arc-backed database state and do not borrow the handle.
|
|
216
245
|
* ---------------------------------------------------------------------- */
|
|
217
246
|
|
|
218
247
|
typedef struct TalaDbJob TalaDbJob;
|
package/ios/TalaDB.mm
CHANGED
|
@@ -32,12 +32,6 @@
|
|
|
32
32
|
|
|
33
33
|
using namespace facebook::jsi;
|
|
34
34
|
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
// Global handle — one database per process
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
|
|
39
|
-
static TalaDbHandle *gHandle = nullptr;
|
|
40
|
-
|
|
41
35
|
// ---------------------------------------------------------------------------
|
|
42
36
|
// TalaDB — Obj-C TurboModule
|
|
43
37
|
// ---------------------------------------------------------------------------
|
|
@@ -51,27 +45,26 @@ RCT_EXPORT_MODULE(TalaDB)
|
|
|
51
45
|
|
|
52
46
|
// ---- Class method: open DB and install the JSI HostObject ----------------
|
|
53
47
|
|
|
54
|
-
+ (
|
|
48
|
+
+ (NSString * _Nullable)installInRuntime:(facebook::jsi::Runtime &)rt
|
|
55
49
|
dbPath:(NSString *)path
|
|
56
50
|
configJson:(NSString * _Nullable)configJson {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
51
|
+
// Release any previous HostObject before reopening the same redb file.
|
|
52
|
+
rt.global().setProperty(rt, "__TalaDB__", Value::undefined());
|
|
53
|
+
TalaDbHandle *handle = nullptr;
|
|
62
54
|
if (configJson != nil) {
|
|
63
|
-
|
|
55
|
+
handle = taladb_open_with_config(path.UTF8String, configJson.UTF8String);
|
|
64
56
|
} else {
|
|
65
|
-
|
|
57
|
+
handle = taladb_open(path.UTF8String);
|
|
66
58
|
}
|
|
67
59
|
|
|
68
|
-
if (!
|
|
69
|
-
|
|
70
|
-
return;
|
|
60
|
+
if (!handle) {
|
|
61
|
+
const char *raw = taladb_last_error();
|
|
62
|
+
return raw ? [NSString stringWithUTF8String:raw] : @"failed to open TalaDB";
|
|
71
63
|
}
|
|
72
64
|
|
|
73
|
-
taladb::TalaDBHostObject::install(rt,
|
|
65
|
+
taladb::TalaDBHostObject::install(rt, handle);
|
|
74
66
|
NSLog(@"[TalaDB] Installed JSI HostObject — db: %@", path);
|
|
67
|
+
return nil;
|
|
75
68
|
}
|
|
76
69
|
|
|
77
70
|
// ---- initialize(dbName) → Promise<void> ---------------------------------
|
|
@@ -81,9 +74,29 @@ RCT_EXPORT_METHOD(initialize:(NSString *)dbName
|
|
|
81
74
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
82
75
|
reject:(RCTPromiseRejectBlock)reject) {
|
|
83
76
|
@try {
|
|
84
|
-
NSString *
|
|
77
|
+
NSString *support = [NSSearchPathForDirectoriesInDomains(
|
|
78
|
+
NSApplicationSupportDirectory, NSUserDomainMask, YES) firstObject];
|
|
79
|
+
[[NSFileManager defaultManager] createDirectoryAtPath:support
|
|
80
|
+
withIntermediateDirectories:YES
|
|
81
|
+
attributes:@{NSFileProtectionKey: NSFileProtectionCompleteUntilFirstUserAuthentication}
|
|
82
|
+
error:nil];
|
|
83
|
+
NSString *dbPath = [support stringByAppendingPathComponent:dbName];
|
|
84
|
+
NSString *documents = [NSSearchPathForDirectoriesInDomains(
|
|
85
85
|
NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
|
|
86
|
-
NSString *
|
|
86
|
+
NSString *legacyPath = [documents stringByAppendingPathComponent:dbName];
|
|
87
|
+
if (![[NSFileManager defaultManager] fileExistsAtPath:dbPath] &&
|
|
88
|
+
[[NSFileManager defaultManager] fileExistsAtPath:legacyPath]) {
|
|
89
|
+
NSError *migrationError = nil;
|
|
90
|
+
if (![[NSFileManager defaultManager] moveItemAtPath:legacyPath toPath:dbPath error:&migrationError]) {
|
|
91
|
+
reject(@"TALADB_MIGRATION_ERROR", migrationError.localizedDescription, migrationError);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
NSString *legacySalt = [legacyPath stringByAppendingString:@".taladb-salt"];
|
|
95
|
+
NSString *newSalt = [dbPath stringByAppendingString:@".taladb-salt"];
|
|
96
|
+
if ([[NSFileManager defaultManager] fileExistsAtPath:legacySalt]) {
|
|
97
|
+
[[NSFileManager defaultManager] moveItemAtPath:legacySalt toPath:newSalt error:nil];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
87
100
|
|
|
88
101
|
RCTCxxBridge *bridge = (RCTCxxBridge *)[RCTBridge currentBridge];
|
|
89
102
|
if (!bridge || !bridge.runtime) {
|
|
@@ -92,12 +105,22 @@ RCT_EXPORT_METHOD(initialize:(NSString *)dbName
|
|
|
92
105
|
}
|
|
93
106
|
|
|
94
107
|
// Install the HostObject on the JS thread
|
|
95
|
-
bridge.jsCallInvoker->invokeAsync([bridge, dbPath, configJson]() {
|
|
108
|
+
bridge.jsCallInvoker->invokeAsync([bridge, dbPath, configJson, resolve, reject]() {
|
|
96
109
|
auto &rt = *(Runtime *)bridge.runtime;
|
|
97
|
-
[TalaDB installInRuntime:rt dbPath:dbPath configJson:configJson];
|
|
110
|
+
NSString *error = [TalaDB installInRuntime:rt dbPath:dbPath configJson:configJson];
|
|
111
|
+
if (error) reject(@"TALADB_INSTALL_ERROR", error, nil);
|
|
112
|
+
else {
|
|
113
|
+
NSDictionary *protection = @{NSFileProtectionKey: NSFileProtectionCompleteUntilFirstUserAuthentication};
|
|
114
|
+
[[NSFileManager defaultManager] setAttributes:protection ofItemAtPath:dbPath error:nil];
|
|
115
|
+
NSString *saltPath = [dbPath stringByAppendingString:@".taladb-salt"];
|
|
116
|
+
[[NSFileManager defaultManager] setAttributes:protection ofItemAtPath:saltPath error:nil];
|
|
117
|
+
NSURL *dbURL = [NSURL fileURLWithPath:dbPath];
|
|
118
|
+
[dbURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
|
|
119
|
+
NSURL *saltURL = [NSURL fileURLWithPath:saltPath];
|
|
120
|
+
[saltURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
|
|
121
|
+
resolve(nil);
|
|
122
|
+
}
|
|
98
123
|
});
|
|
99
|
-
|
|
100
|
-
resolve(nil);
|
|
101
124
|
} @catch (NSException *ex) {
|
|
102
125
|
reject(@"TALADB_INIT_ERROR", ex.reason, nil);
|
|
103
126
|
}
|
|
@@ -107,11 +130,16 @@ RCT_EXPORT_METHOD(initialize:(NSString *)dbName
|
|
|
107
130
|
|
|
108
131
|
RCT_EXPORT_METHOD(close:(RCTPromiseResolveBlock)resolve
|
|
109
132
|
reject:(RCTPromiseRejectBlock)reject) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
133
|
+
RCTCxxBridge *bridge = (RCTCxxBridge *)[RCTBridge currentBridge];
|
|
134
|
+
if (!bridge || !bridge.runtime) {
|
|
135
|
+
reject(@"TALADB_NO_BRIDGE", @"JSI bridge not available", nil);
|
|
136
|
+
return;
|
|
113
137
|
}
|
|
114
|
-
resolve(
|
|
138
|
+
bridge.jsCallInvoker->invokeAsync([bridge, resolve]() {
|
|
139
|
+
auto &rt = *(Runtime *)bridge.runtime;
|
|
140
|
+
rt.global().setProperty(rt, "__TalaDB__", Value::undefined());
|
|
141
|
+
resolve(nil);
|
|
142
|
+
});
|
|
115
143
|
}
|
|
116
144
|
|
|
117
145
|
// ---- Synchronous stubs — all real work goes through the JSI HostObject ---
|
|
@@ -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>
|
|
@@ -38,6 +38,7 @@ TalaDBHostObject::TalaDBHostObject(TalaDbHandle *db) : db_(db) {}
|
|
|
38
38
|
|
|
39
39
|
TalaDBHostObject::~TalaDBHostObject() {
|
|
40
40
|
if (db_) {
|
|
41
|
+
taladb_sync_flush(db_, 5000);
|
|
41
42
|
taladb_close(db_);
|
|
42
43
|
db_ = nullptr;
|
|
43
44
|
}
|
|
@@ -245,12 +246,15 @@ std::vector<PropNameID> TalaDBHostObject::getPropertyNames(Runtime &rt) {
|
|
|
245
246
|
"deleteOne", "deleteMany",
|
|
246
247
|
"count",
|
|
247
248
|
"aggregate",
|
|
249
|
+
"exportChanges", "importChanges", "listCollectionNames",
|
|
248
250
|
"createIndex", "dropIndex",
|
|
251
|
+
"createCompoundIndex", "dropCompoundIndex",
|
|
249
252
|
"createFtsIndex", "dropFtsIndex",
|
|
250
253
|
"createVectorIndex", "dropVectorIndex", "upgradeVectorIndex",
|
|
251
254
|
"findNearest", "findNearestAsync",
|
|
252
255
|
"findAsync",
|
|
253
256
|
"compact",
|
|
257
|
+
"syncStatus", "flushSync",
|
|
254
258
|
"close",
|
|
255
259
|
};
|
|
256
260
|
std::vector<PropNameID> result;
|
|
@@ -444,6 +448,51 @@ Value TalaDBHostObject::get(Runtime &rt, const PropNameID &propName) {
|
|
|
444
448
|
});
|
|
445
449
|
}
|
|
446
450
|
|
|
451
|
+
// ------------------------------------------------------------------
|
|
452
|
+
// Bidirectional sync — exportChanges / importChanges / listCollectionNames
|
|
453
|
+
// (back JS db.sync(); the runtime-agnostic loop lives in taladb/src/sync.ts)
|
|
454
|
+
// ------------------------------------------------------------------
|
|
455
|
+
if (name == "exportChanges") {
|
|
456
|
+
return Function::createFromHostFunction(
|
|
457
|
+
rt, PropNameID::forAscii(rt, "exportChanges"), 2,
|
|
458
|
+
[this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
459
|
+
if (count < 2) throw JSError(rt, "exportChanges requires 2 arguments");
|
|
460
|
+
auto collectionsJson = stringify(rt, args[0]); // string[] → JSON array
|
|
461
|
+
double sinceMs = args[1].getNumber();
|
|
462
|
+
char *result = taladb_export_changes(db_, collectionsJson.c_str(), sinceMs);
|
|
463
|
+
if (!result) throw JSError(rt, "taladb_export_changes failed");
|
|
464
|
+
std::string json(result);
|
|
465
|
+
taladb_free_string(result);
|
|
466
|
+
// Return the changeset as an opaque string (not parsed) — the
|
|
467
|
+
// JS sync adapter passes it straight to the transport.
|
|
468
|
+
return String::createFromUtf8(rt, json);
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (name == "importChanges") {
|
|
473
|
+
return Function::createFromHostFunction(
|
|
474
|
+
rt, PropNameID::forAscii(rt, "importChanges"), 1,
|
|
475
|
+
[this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
476
|
+
if (count < 1) throw JSError(rt, "importChanges requires 1 argument");
|
|
477
|
+
auto changeset = args[0].getString(rt).utf8(rt);
|
|
478
|
+
int32_t n = taladb_import_changes(db_, changeset.c_str());
|
|
479
|
+
if (n < 0) throw JSError(rt, "taladb_import_changes failed");
|
|
480
|
+
return Value(static_cast<double>(n));
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (name == "listCollectionNames") {
|
|
485
|
+
return Function::createFromHostFunction(
|
|
486
|
+
rt, PropNameID::forAscii(rt, "listCollectionNames"), 0,
|
|
487
|
+
[this](Runtime &rt, const Value &, const Value *, size_t) -> Value {
|
|
488
|
+
char *result = taladb_list_collection_names(db_);
|
|
489
|
+
if (!result) throw JSError(rt, "taladb_list_collection_names failed");
|
|
490
|
+
std::string json(result);
|
|
491
|
+
taladb_free_string(result);
|
|
492
|
+
return parse(rt, json); // JSON array → JS string[]
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
447
496
|
// ------------------------------------------------------------------
|
|
448
497
|
// createIndex / dropIndex / createFtsIndex / dropFtsIndex
|
|
449
498
|
// ------------------------------------------------------------------
|
|
@@ -463,6 +512,24 @@ Value TalaDBHostObject::get(Runtime &rt, const PropNameID &propName) {
|
|
|
463
512
|
});
|
|
464
513
|
}
|
|
465
514
|
|
|
515
|
+
// ------------------------------------------------------------------
|
|
516
|
+
// createCompoundIndex / dropCompoundIndex(collection, fields: string[]): void
|
|
517
|
+
// ------------------------------------------------------------------
|
|
518
|
+
if (name == "createCompoundIndex" || name == "dropCompoundIndex") {
|
|
519
|
+
return Function::createFromHostFunction(
|
|
520
|
+
rt, PropNameID::forUtf8(rt, name), 2,
|
|
521
|
+
[this, name](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
522
|
+
if (count < 2) throw JSError(rt, (name + " requires 2 arguments").c_str());
|
|
523
|
+
auto col = args[0].getString(rt).utf8(rt);
|
|
524
|
+
auto fieldsJson = stringify(rt, args[1]); // string[] → JSON array
|
|
525
|
+
if (name == "createCompoundIndex")
|
|
526
|
+
taladb_create_compound_index(db_, col.c_str(), fieldsJson.c_str());
|
|
527
|
+
else
|
|
528
|
+
taladb_drop_compound_index(db_, col.c_str(), fieldsJson.c_str());
|
|
529
|
+
return Value::undefined();
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
466
533
|
// ------------------------------------------------------------------
|
|
467
534
|
// createVectorIndex(collection, field, dimensions, opts?): void
|
|
468
535
|
// opts: { metric?: 'cosine'|'dot'|'euclidean', hnsw?: HnswOptions }
|
|
@@ -623,6 +690,26 @@ Value TalaDBHostObject::get(Runtime &rt, const PropNameID &propName) {
|
|
|
623
690
|
});
|
|
624
691
|
}
|
|
625
692
|
|
|
693
|
+
if (name == "syncStatus") {
|
|
694
|
+
return Function::createFromHostFunction(
|
|
695
|
+
rt, PropNameID::forAscii(rt, "syncStatus"), 0,
|
|
696
|
+
[this](Runtime &rt, const Value &, const Value *, size_t) -> Value {
|
|
697
|
+
char *raw = taladb_sync_status(db_);
|
|
698
|
+
if (!raw) throw ffiError(rt, "taladb_sync_status failed");
|
|
699
|
+
std::string json(raw); taladb_free_string(raw);
|
|
700
|
+
return parse(rt, json);
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (name == "flushSync") {
|
|
705
|
+
return Function::createFromHostFunction(
|
|
706
|
+
rt, PropNameID::forAscii(rt, "flushSync"), 1,
|
|
707
|
+
[this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
708
|
+
uint64_t timeout = count && args[0].isNumber() ? (uint64_t)args[0].getNumber() : 5000;
|
|
709
|
+
return Value(taladb_sync_flush(db_, timeout) == 1);
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
|
|
626
713
|
// ------------------------------------------------------------------
|
|
627
714
|
// close(): void (synchronous — the destructor does the real work)
|
|
628
715
|
// ------------------------------------------------------------------
|
|
@@ -631,6 +718,7 @@ Value TalaDBHostObject::get(Runtime &rt, const PropNameID &propName) {
|
|
|
631
718
|
rt, PropNameID::forAscii(rt, "close"), 0,
|
|
632
719
|
[this](Runtime &rt, const Value &, const Value *, size_t) -> Value {
|
|
633
720
|
if (db_) {
|
|
721
|
+
taladb_sync_flush(db_, 5000);
|
|
634
722
|
taladb_close(db_);
|
|
635
723
|
db_ = nullptr;
|
|
636
724
|
}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
using namespace facebook::jsi;
|
|
23
23
|
|
|
24
|
-
extern "C" JNIEXPORT
|
|
24
|
+
extern "C" JNIEXPORT jstring JNICALL
|
|
25
25
|
Java_com_taladb_TalaDBModule_nativeInstall(
|
|
26
26
|
JNIEnv *env,
|
|
27
27
|
jobject /* thiz */,
|
|
@@ -29,6 +29,10 @@ Java_com_taladb_TalaDBModule_nativeInstall(
|
|
|
29
29
|
jstring dbPathJ,
|
|
30
30
|
jstring configJsonJ)
|
|
31
31
|
{
|
|
32
|
+
// Release any previous HostObject before reopening the same redb file.
|
|
33
|
+
auto &rt = *reinterpret_cast<Runtime *>(jsContextNativePtr);
|
|
34
|
+
rt.global().setProperty(rt, "__TalaDB__", Value::undefined());
|
|
35
|
+
|
|
32
36
|
// Resolve db path
|
|
33
37
|
const char *dbPathC = env->GetStringUTFChars(dbPathJ, nullptr);
|
|
34
38
|
std::string dbPath(dbPathC);
|
|
@@ -43,11 +47,18 @@ Java_com_taladb_TalaDBModule_nativeInstall(
|
|
|
43
47
|
} else {
|
|
44
48
|
db = taladb_open(dbPath.c_str());
|
|
45
49
|
}
|
|
46
|
-
if (!db)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
if (!db) {
|
|
51
|
+
const char *error = taladb_last_error();
|
|
52
|
+
return env->NewStringUTF(error ? error : "failed to open TalaDB");
|
|
53
|
+
}
|
|
50
54
|
|
|
51
55
|
// Install the JSI HostObject as global.__TalaDB__
|
|
52
56
|
taladb::TalaDBHostObject::install(rt, db);
|
|
57
|
+
return nullptr;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
extern "C" JNIEXPORT void JNICALL
|
|
61
|
+
Java_com_taladb_TalaDBModule_nativeClose(JNIEnv *, jobject, jlong jsContextNativePtr) {
|
|
62
|
+
auto &rt = *reinterpret_cast<Runtime *>(jsContextNativePtr);
|
|
63
|
+
rt.global().setProperty(rt, "__TalaDB__", Value::undefined());
|
|
53
64
|
}
|
|
@@ -154,6 +154,32 @@ char *taladb_aggregate(TalaDbHandle *handle,
|
|
|
154
154
|
const char *collection,
|
|
155
155
|
const char *pipeline_json);
|
|
156
156
|
|
|
157
|
+
/* -------------------------------------------------------------------------
|
|
158
|
+
* Bidirectional sync — changeset export / import (backs JS db.sync())
|
|
159
|
+
* ---------------------------------------------------------------------- */
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Export a changeset for the collections in collections_json (a JSON array of
|
|
163
|
+
* names) with changed_at > since_ms. Returns a JSON string, or NULL on error.
|
|
164
|
+
* Caller must free with taladb_free_string().
|
|
165
|
+
*/
|
|
166
|
+
char *taladb_export_changes(TalaDbHandle *handle,
|
|
167
|
+
const char *collections_json,
|
|
168
|
+
double since_ms);
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Merge a JSON changeset (from a remote peer) via Last-Write-Wins.
|
|
172
|
+
* Returns the number of documents changed, or -1 on error.
|
|
173
|
+
*/
|
|
174
|
+
int32_t taladb_import_changes(TalaDbHandle *handle,
|
|
175
|
+
const char *changeset_json);
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* User collection names (reserved names excluded) as a JSON array string.
|
|
179
|
+
* Returns NULL on error. Caller must free with taladb_free_string().
|
|
180
|
+
*/
|
|
181
|
+
char *taladb_list_collection_names(TalaDbHandle *handle);
|
|
182
|
+
|
|
157
183
|
/* -------------------------------------------------------------------------
|
|
158
184
|
* Index management
|
|
159
185
|
* ---------------------------------------------------------------------- */
|
|
@@ -163,6 +189,10 @@ void taladb_drop_index (TalaDbHandle *handle, const char *collection, const
|
|
|
163
189
|
void taladb_create_fts_index(TalaDbHandle *handle, const char *collection, const char *field);
|
|
164
190
|
void taladb_drop_fts_index (TalaDbHandle *handle, const char *collection, const char *field);
|
|
165
191
|
|
|
192
|
+
/* Compound (multi-field) indexes. fields_json is a JSON array of field names. */
|
|
193
|
+
void taladb_create_compound_index(TalaDbHandle *handle, const char *collection, const char *fields_json);
|
|
194
|
+
void taladb_drop_compound_index (TalaDbHandle *handle, const char *collection, const char *fields_json);
|
|
195
|
+
|
|
166
196
|
/* -------------------------------------------------------------------------
|
|
167
197
|
* Vector index management
|
|
168
198
|
*
|
|
@@ -199,6 +229,9 @@ char *taladb_find_nearest(TalaDbHandle *handle,
|
|
|
199
229
|
size_t top_k,
|
|
200
230
|
const char *filter_json);
|
|
201
231
|
|
|
232
|
+
char *taladb_sync_status(TalaDbHandle *handle);
|
|
233
|
+
int32_t taladb_sync_flush(TalaDbHandle *handle, uint64_t timeout_ms);
|
|
234
|
+
|
|
202
235
|
/* -------------------------------------------------------------------------
|
|
203
236
|
* Async job API — run heavy queries on a background thread.
|
|
204
237
|
*
|
|
@@ -208,11 +241,7 @@ char *taladb_find_nearest(TalaDbHandle *handle,
|
|
|
208
241
|
* while (taladb_job_poll(j) == 0) { } // yield to JS event loop
|
|
209
242
|
* char *json = taladb_job_take_result(j); // frees the job
|
|
210
243
|
*
|
|
211
|
-
*
|
|
212
|
-
* -----------------
|
|
213
|
-
* The handle passed to `*_start` MUST remain valid until the job has been
|
|
214
|
-
* taken (via take_result) or cancelled (via cancel). The C++ HostObject
|
|
215
|
-
* enforces this by not calling taladb_close while jobs are outstanding.
|
|
244
|
+
* Workers clone the Arc-backed database state and do not borrow the handle.
|
|
216
245
|
* ---------------------------------------------------------------------- */
|
|
217
246
|
|
|
218
247
|
typedef struct TalaDbJob TalaDbJob;
|
|
Binary file
|
|
@@ -38,6 +38,7 @@ TalaDBHostObject::TalaDBHostObject(TalaDbHandle *db) : db_(db) {}
|
|
|
38
38
|
|
|
39
39
|
TalaDBHostObject::~TalaDBHostObject() {
|
|
40
40
|
if (db_) {
|
|
41
|
+
taladb_sync_flush(db_, 5000);
|
|
41
42
|
taladb_close(db_);
|
|
42
43
|
db_ = nullptr;
|
|
43
44
|
}
|
|
@@ -245,12 +246,15 @@ std::vector<PropNameID> TalaDBHostObject::getPropertyNames(Runtime &rt) {
|
|
|
245
246
|
"deleteOne", "deleteMany",
|
|
246
247
|
"count",
|
|
247
248
|
"aggregate",
|
|
249
|
+
"exportChanges", "importChanges", "listCollectionNames",
|
|
248
250
|
"createIndex", "dropIndex",
|
|
251
|
+
"createCompoundIndex", "dropCompoundIndex",
|
|
249
252
|
"createFtsIndex", "dropFtsIndex",
|
|
250
253
|
"createVectorIndex", "dropVectorIndex", "upgradeVectorIndex",
|
|
251
254
|
"findNearest", "findNearestAsync",
|
|
252
255
|
"findAsync",
|
|
253
256
|
"compact",
|
|
257
|
+
"syncStatus", "flushSync",
|
|
254
258
|
"close",
|
|
255
259
|
};
|
|
256
260
|
std::vector<PropNameID> result;
|
|
@@ -444,6 +448,51 @@ Value TalaDBHostObject::get(Runtime &rt, const PropNameID &propName) {
|
|
|
444
448
|
});
|
|
445
449
|
}
|
|
446
450
|
|
|
451
|
+
// ------------------------------------------------------------------
|
|
452
|
+
// Bidirectional sync — exportChanges / importChanges / listCollectionNames
|
|
453
|
+
// (back JS db.sync(); the runtime-agnostic loop lives in taladb/src/sync.ts)
|
|
454
|
+
// ------------------------------------------------------------------
|
|
455
|
+
if (name == "exportChanges") {
|
|
456
|
+
return Function::createFromHostFunction(
|
|
457
|
+
rt, PropNameID::forAscii(rt, "exportChanges"), 2,
|
|
458
|
+
[this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
459
|
+
if (count < 2) throw JSError(rt, "exportChanges requires 2 arguments");
|
|
460
|
+
auto collectionsJson = stringify(rt, args[0]); // string[] → JSON array
|
|
461
|
+
double sinceMs = args[1].getNumber();
|
|
462
|
+
char *result = taladb_export_changes(db_, collectionsJson.c_str(), sinceMs);
|
|
463
|
+
if (!result) throw JSError(rt, "taladb_export_changes failed");
|
|
464
|
+
std::string json(result);
|
|
465
|
+
taladb_free_string(result);
|
|
466
|
+
// Return the changeset as an opaque string (not parsed) — the
|
|
467
|
+
// JS sync adapter passes it straight to the transport.
|
|
468
|
+
return String::createFromUtf8(rt, json);
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (name == "importChanges") {
|
|
473
|
+
return Function::createFromHostFunction(
|
|
474
|
+
rt, PropNameID::forAscii(rt, "importChanges"), 1,
|
|
475
|
+
[this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
476
|
+
if (count < 1) throw JSError(rt, "importChanges requires 1 argument");
|
|
477
|
+
auto changeset = args[0].getString(rt).utf8(rt);
|
|
478
|
+
int32_t n = taladb_import_changes(db_, changeset.c_str());
|
|
479
|
+
if (n < 0) throw JSError(rt, "taladb_import_changes failed");
|
|
480
|
+
return Value(static_cast<double>(n));
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (name == "listCollectionNames") {
|
|
485
|
+
return Function::createFromHostFunction(
|
|
486
|
+
rt, PropNameID::forAscii(rt, "listCollectionNames"), 0,
|
|
487
|
+
[this](Runtime &rt, const Value &, const Value *, size_t) -> Value {
|
|
488
|
+
char *result = taladb_list_collection_names(db_);
|
|
489
|
+
if (!result) throw JSError(rt, "taladb_list_collection_names failed");
|
|
490
|
+
std::string json(result);
|
|
491
|
+
taladb_free_string(result);
|
|
492
|
+
return parse(rt, json); // JSON array → JS string[]
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
447
496
|
// ------------------------------------------------------------------
|
|
448
497
|
// createIndex / dropIndex / createFtsIndex / dropFtsIndex
|
|
449
498
|
// ------------------------------------------------------------------
|
|
@@ -463,6 +512,24 @@ Value TalaDBHostObject::get(Runtime &rt, const PropNameID &propName) {
|
|
|
463
512
|
});
|
|
464
513
|
}
|
|
465
514
|
|
|
515
|
+
// ------------------------------------------------------------------
|
|
516
|
+
// createCompoundIndex / dropCompoundIndex(collection, fields: string[]): void
|
|
517
|
+
// ------------------------------------------------------------------
|
|
518
|
+
if (name == "createCompoundIndex" || name == "dropCompoundIndex") {
|
|
519
|
+
return Function::createFromHostFunction(
|
|
520
|
+
rt, PropNameID::forUtf8(rt, name), 2,
|
|
521
|
+
[this, name](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
522
|
+
if (count < 2) throw JSError(rt, (name + " requires 2 arguments").c_str());
|
|
523
|
+
auto col = args[0].getString(rt).utf8(rt);
|
|
524
|
+
auto fieldsJson = stringify(rt, args[1]); // string[] → JSON array
|
|
525
|
+
if (name == "createCompoundIndex")
|
|
526
|
+
taladb_create_compound_index(db_, col.c_str(), fieldsJson.c_str());
|
|
527
|
+
else
|
|
528
|
+
taladb_drop_compound_index(db_, col.c_str(), fieldsJson.c_str());
|
|
529
|
+
return Value::undefined();
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
466
533
|
// ------------------------------------------------------------------
|
|
467
534
|
// createVectorIndex(collection, field, dimensions, opts?): void
|
|
468
535
|
// opts: { metric?: 'cosine'|'dot'|'euclidean', hnsw?: HnswOptions }
|
|
@@ -623,6 +690,26 @@ Value TalaDBHostObject::get(Runtime &rt, const PropNameID &propName) {
|
|
|
623
690
|
});
|
|
624
691
|
}
|
|
625
692
|
|
|
693
|
+
if (name == "syncStatus") {
|
|
694
|
+
return Function::createFromHostFunction(
|
|
695
|
+
rt, PropNameID::forAscii(rt, "syncStatus"), 0,
|
|
696
|
+
[this](Runtime &rt, const Value &, const Value *, size_t) -> Value {
|
|
697
|
+
char *raw = taladb_sync_status(db_);
|
|
698
|
+
if (!raw) throw ffiError(rt, "taladb_sync_status failed");
|
|
699
|
+
std::string json(raw); taladb_free_string(raw);
|
|
700
|
+
return parse(rt, json);
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (name == "flushSync") {
|
|
705
|
+
return Function::createFromHostFunction(
|
|
706
|
+
rt, PropNameID::forAscii(rt, "flushSync"), 1,
|
|
707
|
+
[this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
|
|
708
|
+
uint64_t timeout = count && args[0].isNumber() ? (uint64_t)args[0].getNumber() : 5000;
|
|
709
|
+
return Value(taladb_sync_flush(db_, timeout) == 1);
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
|
|
626
713
|
// ------------------------------------------------------------------
|
|
627
714
|
// close(): void (synchronous — the destructor does the real work)
|
|
628
715
|
// ------------------------------------------------------------------
|
|
@@ -631,6 +718,7 @@ Value TalaDBHostObject::get(Runtime &rt, const PropNameID &propName) {
|
|
|
631
718
|
rt, PropNameID::forAscii(rt, "close"), 0,
|
|
632
719
|
[this](Runtime &rt, const Value &, const Value *, size_t) -> Value {
|
|
633
720
|
if (db_) {
|
|
721
|
+
taladb_sync_flush(db_, 5000);
|
|
634
722
|
taladb_close(db_);
|
|
635
723
|
db_ = nullptr;
|
|
636
724
|
}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
using namespace facebook::jsi;
|
|
23
23
|
|
|
24
|
-
extern "C" JNIEXPORT
|
|
24
|
+
extern "C" JNIEXPORT jstring JNICALL
|
|
25
25
|
Java_com_taladb_TalaDBModule_nativeInstall(
|
|
26
26
|
JNIEnv *env,
|
|
27
27
|
jobject /* thiz */,
|
|
@@ -29,6 +29,10 @@ Java_com_taladb_TalaDBModule_nativeInstall(
|
|
|
29
29
|
jstring dbPathJ,
|
|
30
30
|
jstring configJsonJ)
|
|
31
31
|
{
|
|
32
|
+
// Release any previous HostObject before reopening the same redb file.
|
|
33
|
+
auto &rt = *reinterpret_cast<Runtime *>(jsContextNativePtr);
|
|
34
|
+
rt.global().setProperty(rt, "__TalaDB__", Value::undefined());
|
|
35
|
+
|
|
32
36
|
// Resolve db path
|
|
33
37
|
const char *dbPathC = env->GetStringUTFChars(dbPathJ, nullptr);
|
|
34
38
|
std::string dbPath(dbPathC);
|
|
@@ -43,11 +47,18 @@ Java_com_taladb_TalaDBModule_nativeInstall(
|
|
|
43
47
|
} else {
|
|
44
48
|
db = taladb_open(dbPath.c_str());
|
|
45
49
|
}
|
|
46
|
-
if (!db)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
if (!db) {
|
|
51
|
+
const char *error = taladb_last_error();
|
|
52
|
+
return env->NewStringUTF(error ? error : "failed to open TalaDB");
|
|
53
|
+
}
|
|
50
54
|
|
|
51
55
|
// Install the JSI HostObject as global.__TalaDB__
|
|
52
56
|
taladb::TalaDBHostObject::install(rt, db);
|
|
57
|
+
return nullptr;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
extern "C" JNIEXPORT void JNICALL
|
|
61
|
+
Java_com_taladb_TalaDBModule_nativeClose(JNIEnv *, jobject, jlong jsContextNativePtr) {
|
|
62
|
+
auto &rt = *reinterpret_cast<Runtime *>(jsContextNativePtr);
|
|
63
|
+
rt.global().setProperty(rt, "__TalaDB__", Value::undefined());
|
|
53
64
|
}
|
|
@@ -154,6 +154,32 @@ char *taladb_aggregate(TalaDbHandle *handle,
|
|
|
154
154
|
const char *collection,
|
|
155
155
|
const char *pipeline_json);
|
|
156
156
|
|
|
157
|
+
/* -------------------------------------------------------------------------
|
|
158
|
+
* Bidirectional sync — changeset export / import (backs JS db.sync())
|
|
159
|
+
* ---------------------------------------------------------------------- */
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Export a changeset for the collections in collections_json (a JSON array of
|
|
163
|
+
* names) with changed_at > since_ms. Returns a JSON string, or NULL on error.
|
|
164
|
+
* Caller must free with taladb_free_string().
|
|
165
|
+
*/
|
|
166
|
+
char *taladb_export_changes(TalaDbHandle *handle,
|
|
167
|
+
const char *collections_json,
|
|
168
|
+
double since_ms);
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Merge a JSON changeset (from a remote peer) via Last-Write-Wins.
|
|
172
|
+
* Returns the number of documents changed, or -1 on error.
|
|
173
|
+
*/
|
|
174
|
+
int32_t taladb_import_changes(TalaDbHandle *handle,
|
|
175
|
+
const char *changeset_json);
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* User collection names (reserved names excluded) as a JSON array string.
|
|
179
|
+
* Returns NULL on error. Caller must free with taladb_free_string().
|
|
180
|
+
*/
|
|
181
|
+
char *taladb_list_collection_names(TalaDbHandle *handle);
|
|
182
|
+
|
|
157
183
|
/* -------------------------------------------------------------------------
|
|
158
184
|
* Index management
|
|
159
185
|
* ---------------------------------------------------------------------- */
|
|
@@ -163,6 +189,10 @@ void taladb_drop_index (TalaDbHandle *handle, const char *collection, const
|
|
|
163
189
|
void taladb_create_fts_index(TalaDbHandle *handle, const char *collection, const char *field);
|
|
164
190
|
void taladb_drop_fts_index (TalaDbHandle *handle, const char *collection, const char *field);
|
|
165
191
|
|
|
192
|
+
/* Compound (multi-field) indexes. fields_json is a JSON array of field names. */
|
|
193
|
+
void taladb_create_compound_index(TalaDbHandle *handle, const char *collection, const char *fields_json);
|
|
194
|
+
void taladb_drop_compound_index (TalaDbHandle *handle, const char *collection, const char *fields_json);
|
|
195
|
+
|
|
166
196
|
/* -------------------------------------------------------------------------
|
|
167
197
|
* Vector index management
|
|
168
198
|
*
|
|
@@ -199,6 +229,9 @@ char *taladb_find_nearest(TalaDbHandle *handle,
|
|
|
199
229
|
size_t top_k,
|
|
200
230
|
const char *filter_json);
|
|
201
231
|
|
|
232
|
+
char *taladb_sync_status(TalaDbHandle *handle);
|
|
233
|
+
int32_t taladb_sync_flush(TalaDbHandle *handle, uint64_t timeout_ms);
|
|
234
|
+
|
|
202
235
|
/* -------------------------------------------------------------------------
|
|
203
236
|
* Async job API — run heavy queries on a background thread.
|
|
204
237
|
*
|
|
@@ -208,11 +241,7 @@ char *taladb_find_nearest(TalaDbHandle *handle,
|
|
|
208
241
|
* while (taladb_job_poll(j) == 0) { } // yield to JS event loop
|
|
209
242
|
* char *json = taladb_job_take_result(j); // frees the job
|
|
210
243
|
*
|
|
211
|
-
*
|
|
212
|
-
* -----------------
|
|
213
|
-
* The handle passed to `*_start` MUST remain valid until the job has been
|
|
214
|
-
* taken (via take_result) or cancelled (via cancel). The C++ HostObject
|
|
215
|
-
* enforces this by not calling taladb_close while jobs are outstanding.
|
|
244
|
+
* Workers clone the Arc-backed database state and do not borrow the handle.
|
|
216
245
|
* ---------------------------------------------------------------------- */
|
|
217
246
|
|
|
218
247
|
typedef struct TalaDbJob TalaDbJob;
|
|
Binary file
|
package/package.json
CHANGED
package/src/index.tsx
CHANGED
|
@@ -35,6 +35,10 @@ export const TalaDBModule = {
|
|
|
35
35
|
NativeTalaDB.initialize(dbName, configJson),
|
|
36
36
|
/** Close the database gracefully. */
|
|
37
37
|
close: () => NativeTalaDB.close(),
|
|
38
|
+
/** HTTP push events dropped by backpressure or failed after retries. */
|
|
39
|
+
syncStatus: () => native().syncStatus(),
|
|
40
|
+
/** Wait for HTTP push work accepted before this call. */
|
|
41
|
+
flushSync: (timeoutMs = 5000) => native().flushSync(timeoutMs),
|
|
38
42
|
};
|
|
39
43
|
|
|
40
44
|
// ---------------------------------------------------------------------------
|
|
@@ -67,9 +71,35 @@ export interface Collection<T extends Document = Document> {
|
|
|
67
71
|
|
|
68
72
|
export interface DB {
|
|
69
73
|
collection<T extends Document = Document>(name: string): Collection<T>;
|
|
74
|
+
syncStatus(): { dropped: number; failed: number };
|
|
75
|
+
flushSync(timeoutMs?: number): boolean;
|
|
70
76
|
close(): Promise<void>;
|
|
71
77
|
}
|
|
72
78
|
|
|
79
|
+
interface JsiTalaDB {
|
|
80
|
+
insert(collection: string, doc: Object): string;
|
|
81
|
+
insertMany(collection: string, docs: Object[]): string[];
|
|
82
|
+
find(collection: string, filter: Object | null): Object[];
|
|
83
|
+
findOne(collection: string, filter: Object | null): Object | null;
|
|
84
|
+
updateOne(collection: string, filter: Object, update: Object): boolean;
|
|
85
|
+
updateMany(collection: string, filter: Object, update: Object): number;
|
|
86
|
+
deleteOne(collection: string, filter: Object): boolean;
|
|
87
|
+
deleteMany(collection: string, filter: Object): number;
|
|
88
|
+
count(collection: string, filter: Object | null): number;
|
|
89
|
+
createIndex(collection: string, field: string): void;
|
|
90
|
+
dropIndex(collection: string, field: string): void;
|
|
91
|
+
createFtsIndex(collection: string, field: string): void;
|
|
92
|
+
dropFtsIndex(collection: string, field: string): void;
|
|
93
|
+
syncStatus(): { dropped: number; failed: number };
|
|
94
|
+
flushSync(timeoutMs?: number): boolean;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function native(): JsiTalaDB {
|
|
98
|
+
const host = (globalThis as { __TalaDB__?: JsiTalaDB }).__TalaDB__;
|
|
99
|
+
if (!host) throw new Error('TalaDB is not initialized; await TalaDBModule.initialize() first');
|
|
100
|
+
return host;
|
|
101
|
+
}
|
|
102
|
+
|
|
73
103
|
// ---------------------------------------------------------------------------
|
|
74
104
|
// openDB — synchronous DB handle (after initialize() has been called)
|
|
75
105
|
// ---------------------------------------------------------------------------
|
|
@@ -82,25 +112,27 @@ export interface DB {
|
|
|
82
112
|
*/
|
|
83
113
|
function collection<T extends Document>(colName: string): Collection<T> {
|
|
84
114
|
return {
|
|
85
|
-
insert: (doc) =>
|
|
86
|
-
insertMany: (docs) =>
|
|
87
|
-
find: (filter?) =>
|
|
88
|
-
findOne: (filter) =>
|
|
89
|
-
updateOne: (filter, update) =>
|
|
90
|
-
updateMany: (filter, update) =>
|
|
91
|
-
deleteOne: (filter) =>
|
|
92
|
-
deleteMany: (filter) =>
|
|
93
|
-
count: (filter?) =>
|
|
94
|
-
createIndex: (field) =>
|
|
95
|
-
dropIndex: (field) =>
|
|
96
|
-
createFtsIndex: (field) =>
|
|
97
|
-
dropFtsIndex: (field) =>
|
|
115
|
+
insert: (doc) => native().insert(colName, doc as Object),
|
|
116
|
+
insertMany: (docs) => native().insertMany(colName, docs as Object[]),
|
|
117
|
+
find: (filter?) => native().find(colName, filter ?? null) as T[],
|
|
118
|
+
findOne: (filter) => native().findOne(colName, filter) as T | null,
|
|
119
|
+
updateOne: (filter, update) => native().updateOne(colName, filter, update),
|
|
120
|
+
updateMany: (filter, update) => native().updateMany(colName, filter, update),
|
|
121
|
+
deleteOne: (filter) => native().deleteOne(colName, filter),
|
|
122
|
+
deleteMany: (filter) => native().deleteMany(colName, filter),
|
|
123
|
+
count: (filter?) => native().count(colName, filter ?? null),
|
|
124
|
+
createIndex: (field) => native().createIndex(colName, field),
|
|
125
|
+
dropIndex: (field) => native().dropIndex(colName, field),
|
|
126
|
+
createFtsIndex: (field) => native().createFtsIndex(colName, field),
|
|
127
|
+
dropFtsIndex: (field) => native().dropFtsIndex(colName, field),
|
|
98
128
|
};
|
|
99
129
|
}
|
|
100
130
|
|
|
101
131
|
export function openDB(_dbName: string): DB {
|
|
102
132
|
return {
|
|
103
133
|
collection,
|
|
134
|
+
syncStatus: () => native().syncStatus(),
|
|
135
|
+
flushSync: (timeoutMs = 5000) => native().flushSync(timeoutMs),
|
|
104
136
|
close: () => NativeTalaDB.close(),
|
|
105
137
|
};
|
|
106
138
|
}
|