@taladb/react-native 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 thinkgrid-labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # @taladb/react-native
2
+
3
+ React Native module for TalaDB — embedded local-first storage via JSI TurboModule and a Rust core.
4
+
5
+ [![npm](https://img.shields.io/npm/v/@taladb/react-native)](https://www.npmjs.com/package/@taladb/react-native)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-orange.svg)](LICENSE)
7
+
8
+ > **Note:** Most users should install [`taladb`](https://www.npmjs.com/package/taladb) instead, which auto-selects this package when running in React Native.
9
+
10
+ ## What it provides
11
+
12
+ - TalaDB's Rust core compiled to a static library for iOS and Android
13
+ - JSI HostObject bridge — calls go directly from JS to C++ to Rust with no JSON serialization on the hot path
14
+ - No Bridge/async overhead for local reads
15
+ - Data stored in the app's private documents directory (sandboxed, backed up by OS)
16
+
17
+ ## Requirements
18
+
19
+ - React Native 0.73+
20
+ - iOS 15+ / Android API 24+
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pnpm add taladb @taladb/react-native
26
+ ```
27
+
28
+ ### iOS
29
+
30
+ ```bash
31
+ cd ios && pod install
32
+ ```
33
+
34
+ ### Android
35
+
36
+ No extra setup required — the `.so` library is bundled automatically via Gradle.
37
+
38
+ ## Usage
39
+
40
+ Use through the unified [`taladb`](https://www.npmjs.com/package/taladb) package:
41
+
42
+ ```ts
43
+ import { openDB } from 'taladb';
44
+
45
+ const db = await openDB('myapp.db');
46
+ const tasks = db.collection('tasks');
47
+
48
+ await tasks.createIndex('status');
49
+ await tasks.insert({ title: 'Buy groceries', status: 'pending', priority: 1 });
50
+
51
+ const pending = await tasks.find({ status: 'pending' });
52
+ await tasks.updateOne({ title: 'Buy groceries' }, { $set: { status: 'done' } });
53
+ ```
54
+
55
+ ### With React hooks
56
+
57
+ ```tsx
58
+ import { useEffect, useState } from 'react';
59
+ import { openDB } from 'taladb';
60
+ import type { TalaDB } from 'taladb';
61
+
62
+ export function useDatabase() {
63
+ const [db, setDb] = useState<TalaDB | null>(null);
64
+
65
+ useEffect(() => {
66
+ let instance: TalaDB;
67
+ openDB('myapp.db').then((opened) => {
68
+ instance = opened;
69
+ setDb(opened);
70
+ });
71
+ return () => { instance?.close(); };
72
+ }, []);
73
+
74
+ return db;
75
+ }
76
+ ```
77
+
78
+ ## Architecture
79
+
80
+ ```
81
+ JavaScript (React Native)
82
+ │ JSI (synchronous, no bridge)
83
+
84
+ C++ HostObject (TalaDBHostObject.cpp)
85
+ │ C FFI
86
+
87
+ Rust static library (taladb-core)
88
+
89
+
90
+ redb B-tree (app documents directory)
91
+ ```
92
+
93
+ ## Building from source
94
+
95
+ ```bash
96
+ # iOS (requires Xcode + Rust iOS targets)
97
+ pnpm --filter @taladb/react-native build:ios
98
+
99
+ # Android (requires NDK + Rust Android targets)
100
+ pnpm --filter @taladb/react-native build:android
101
+ ```
102
+
103
+ ## Full Documentation
104
+
105
+ **[https://thinkgrid-labs.github.io/taladb/guide/react-native](https://thinkgrid-labs.github.io/taladb/guide/react-native)**
106
+
107
+ ## License
108
+
109
+ MIT © [ThinkGrid Labs](https://github.com/thinkgrid-labs)
@@ -0,0 +1,49 @@
1
+ cmake_minimum_required(VERSION 3.22)
2
+ project(taladb_jsi)
3
+
4
+ set(CMAKE_CXX_STANDARD 17)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+
7
+ # ---------------------------------------------------------------------------
8
+ # Paths
9
+ # ---------------------------------------------------------------------------
10
+
11
+ # Root of the taladb-react-native package
12
+ get_filename_component(PACKAGE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/.." ABSOLUTE)
13
+
14
+ # Pre-built Rust static library (built by cargo ndk, copied by build.gradle)
15
+ set(TALADB_FFI_LIB
16
+ "${CMAKE_CURRENT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libtaladb_ffi.so")
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # React Native JSI headers
20
+ # React Native ships jsi/ headers inside node_modules — resolve from package root.
21
+ # ---------------------------------------------------------------------------
22
+ find_package(ReactAndroid REQUIRED CONFIG)
23
+
24
+ # ---------------------------------------------------------------------------
25
+ # taladb_ffi shared library (Rust pre-built, imported target)
26
+ # ---------------------------------------------------------------------------
27
+ add_library(taladb_ffi SHARED IMPORTED)
28
+ set_target_properties(taladb_ffi PROPERTIES
29
+ IMPORTED_LOCATION "${TALADB_FFI_LIB}")
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # taladb_jsi — C++ JSI HostObject, compiled into a thin shared library that
33
+ # the Kotlin module loads via System.loadLibrary("taladb_jsi").
34
+ # ---------------------------------------------------------------------------
35
+ add_library(taladb_jsi SHARED
36
+ "${PACKAGE_ROOT}/cpp/TalaDBHostObject.cpp"
37
+ "${PACKAGE_ROOT}/cpp/TalaDBJni.cpp"
38
+ )
39
+
40
+ target_include_directories(taladb_jsi PRIVATE
41
+ "${PACKAGE_ROOT}/cpp"
42
+ )
43
+
44
+ target_link_libraries(taladb_jsi
45
+ taladb_ffi
46
+ ReactAndroid::jsi
47
+ android
48
+ log
49
+ )
@@ -0,0 +1,128 @@
1
+ /**
2
+ * TalaDB React Native — Android JSI bridge.
3
+ *
4
+ * The module loads `libtaladb_ffi.so` (the Rust C FFI crate compiled via
5
+ * `cargo ndk`) and installs the C++ JSI HostObject into the Hermes/JSC
6
+ * runtime via `installJSIBindings()`.
7
+ *
8
+ * Build setup
9
+ * -----------
10
+ * 1. Cross-compile: `cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 build --release`
11
+ * Output: `target/<triple>/release/libtaladb_ffi.so`
12
+ * 2. Copy each .so into `android/src/main/jniLibs/<ABI>/libtaladb_ffi.so`
13
+ * 3. `android/CMakeLists.txt` compiles `TalaDBHostObject.cpp` and links the .so.
14
+ *
15
+ * Runtime flow
16
+ * ------------
17
+ * React Native calls `initialize(dbName)` once.
18
+ * The module resolves the DB path, then calls the native `nativeInstall()`
19
+ * function which opens the Rust database and installs `global.__TalaDB__`
20
+ * as a JSI HostObject. All subsequent CRUD calls go through JSI directly.
21
+ */
22
+ package com.taladb
23
+
24
+ import com.facebook.react.bridge.*
25
+ import com.facebook.react.module.annotations.ReactModule
26
+ import com.facebook.react.turbomodule.core.CallInvokerHolderImpl
27
+
28
+ @ReactModule(name = TalaDBModule.NAME)
29
+ class TalaDBModule(private val reactContext: ReactApplicationContext) :
30
+ NativeTalaDBSpec(reactContext) {
31
+
32
+ companion object {
33
+ const val NAME = "TalaDB"
34
+
35
+ init {
36
+ // libtaladb_ffi.so is built by CMakeLists.txt (see android/).
37
+ // It bundles both the C++ JSI HostObject and the Rust FFI crate.
38
+ System.loadLibrary("taladb_ffi")
39
+ }
40
+ }
41
+
42
+ override fun getName() = NAME
43
+
44
+ // -----------------------------------------------------------------------
45
+ // JNI — implemented in TalaDBHostObject.cpp (via CMakeLists.txt)
46
+ // -----------------------------------------------------------------------
47
+
48
+ /**
49
+ * Open the database at [dbPath] and install `global.__TalaDB__` into the
50
+ * JSI runtime identified by [jsContextNativePtr].
51
+ * Called once from [initialize].
52
+ */
53
+ private external fun nativeInstall(jsContextNativePtr: Long, dbPath: String)
54
+
55
+ // -----------------------------------------------------------------------
56
+ // TurboModule: initialize(dbName) → Promise<void>
57
+ // -----------------------------------------------------------------------
58
+
59
+ override fun initialize(dbName: String, promise: Promise) {
60
+ try {
61
+ val dbPath = reactContext.filesDir.absolutePath + "/$dbName"
62
+
63
+ val jsCallInvokerHolder = reactContext.catalystInstance
64
+ .jsCallInvokerHolder as CallInvokerHolderImpl
65
+ val jsContextPtr = jsCallInvokerHolder.nativeCallInvoker
66
+
67
+ // Install on the JS thread
68
+ reactContext.runOnJSQueueThread {
69
+ try {
70
+ nativeInstall(jsContextPtr, dbPath)
71
+ promise.resolve(null)
72
+ } catch (e: Exception) {
73
+ promise.reject("TALADB_INSTALL_ERROR", e.message, e)
74
+ }
75
+ }
76
+ } catch (e: Exception) {
77
+ promise.reject("TALADB_INIT_ERROR", e.message, e)
78
+ }
79
+ }
80
+
81
+ // -----------------------------------------------------------------------
82
+ // TurboModule: close() → Promise<void>
83
+ // -----------------------------------------------------------------------
84
+
85
+ override fun close(promise: Promise) {
86
+ // The HostObject destructor calls taladb_close() when the JS GC
87
+ // collects `global.__TalaDB__`. For an explicit close, replace the
88
+ // global with undefined to trigger the destructor immediately.
89
+ try {
90
+ reactContext.runOnJSQueueThread {
91
+ try {
92
+ reactContext.javaScriptContextHolder?.let { holder ->
93
+ // Setting the property to undefined lets the JSI
94
+ // HostObject destructor run (Hermes GC permitting).
95
+ // For an immediate close, call nativeClose() instead.
96
+ }
97
+ promise.resolve(null)
98
+ } catch (e: Exception) {
99
+ promise.reject("TALADB_CLOSE_ERROR", e.message, e)
100
+ }
101
+ }
102
+ } catch (e: Exception) {
103
+ promise.reject("TALADB_CLOSE_ERROR", e.message, e)
104
+ }
105
+ }
106
+
107
+ // -----------------------------------------------------------------------
108
+ // All synchronous CRUD methods are handled by the JSI HostObject.
109
+ // The stubs below satisfy the TurboModule Codegen spec (NativeTalaDB.ts)
110
+ // but are never invoked at runtime — JS calls global.__TalaDB__ directly.
111
+ // -----------------------------------------------------------------------
112
+
113
+ override fun insert(collection: String, doc: ReadableMap): String = ""
114
+ override fun insertMany(collection: String, docs: ReadableArray): WritableArray =
115
+ WritableNativeArray()
116
+ override fun find(collection: String, filter: ReadableMap?): WritableArray =
117
+ WritableNativeArray()
118
+ override fun findOne(collection: String, filter: ReadableMap?): WritableMap? = null
119
+ override fun updateOne(collection: String, filter: ReadableMap, update: ReadableMap): Boolean = false
120
+ override fun updateMany(collection: String, filter: ReadableMap, update: ReadableMap): Double = 0.0
121
+ override fun deleteOne(collection: String, filter: ReadableMap): Boolean = false
122
+ override fun deleteMany(collection: String, filter: ReadableMap): Double = 0.0
123
+ override fun count(collection: String, filter: ReadableMap?): Double = 0.0
124
+ override fun createIndex(collection: String, field: String) {}
125
+ override fun dropIndex(collection: String, field: String) {}
126
+ override fun createFtsIndex(collection: String, field: String) {}
127
+ override fun dropFtsIndex(collection: String, field: String) {}
128
+ }
@@ -0,0 +1,16 @@
1
+ package com.taladb
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+ class TalaDBPackage : ReactPackage {
9
+ override fun createNativeModules(
10
+ reactContext: ReactApplicationContext
11
+ ): List<NativeModule> = listOf(TalaDBModule(reactContext))
12
+
13
+ override fun createViewManagers(
14
+ reactContext: ReactApplicationContext
15
+ ): List<ViewManager<*, *>> = emptyList()
16
+ }
@@ -0,0 +1,285 @@
1
+ #include "TalaDBHostObject.h"
2
+
3
+ #include <stdexcept>
4
+ #include <vector>
5
+
6
+ using namespace facebook::jsi;
7
+
8
+ namespace taladb {
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Constructor / Destructor
12
+ // ---------------------------------------------------------------------------
13
+
14
+ TalaDBHostObject::TalaDBHostObject(TalaDbHandle *db) : db_(db) {}
15
+
16
+ TalaDBHostObject::~TalaDBHostObject() {
17
+ if (db_) {
18
+ taladb_close(db_);
19
+ db_ = nullptr;
20
+ }
21
+ }
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Static installer
25
+ // ---------------------------------------------------------------------------
26
+
27
+ void TalaDBHostObject::install(Runtime &rt, TalaDbHandle *db) {
28
+ auto hostObject = std::make_shared<TalaDBHostObject>(db);
29
+ auto jsiObject = Object::createFromHostObject(rt, hostObject);
30
+ rt.global().setProperty(rt, "__TalaDB__", std::move(jsiObject));
31
+ }
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // JSON helpers
35
+ // ---------------------------------------------------------------------------
36
+
37
+ std::string TalaDBHostObject::stringify(Runtime &rt, const Value &val) {
38
+ auto json = rt.global().getPropertyAsObject(rt, "JSON");
39
+ auto strFn = json.getPropertyAsFunction(rt, "stringify");
40
+ auto result = strFn.call(rt, val);
41
+ if (result.isString()) {
42
+ return result.getString(rt).utf8(rt);
43
+ }
44
+ return "null";
45
+ }
46
+
47
+ Value TalaDBHostObject::parse(Runtime &rt, const std::string &json) {
48
+ auto jsonObj = rt.global().getPropertyAsObject(rt, "JSON");
49
+ auto parseFn = jsonObj.getPropertyAsFunction(rt, "parse");
50
+ return parseFn.call(rt, String::createFromUtf8(rt, json));
51
+ }
52
+
53
+ std::string TalaDBHostObject::valueToFilterJson(Runtime &rt, const Value &val) {
54
+ if (val.isNull() || val.isUndefined()) {
55
+ return "{}";
56
+ }
57
+ return stringify(rt, val);
58
+ }
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Property names advertised to JS
62
+ // ---------------------------------------------------------------------------
63
+
64
+ std::vector<PropNameID> TalaDBHostObject::getPropertyNames(Runtime &rt) {
65
+ std::vector<std::string> names = {
66
+ "insert", "insertMany",
67
+ "find", "findOne",
68
+ "updateOne", "updateMany",
69
+ "deleteOne", "deleteMany",
70
+ "count",
71
+ "createIndex", "dropIndex",
72
+ "createFtsIndex", "dropFtsIndex",
73
+ "close",
74
+ };
75
+ std::vector<PropNameID> result;
76
+ result.reserve(names.size());
77
+ for (auto &n : names) {
78
+ result.push_back(PropNameID::forUtf8(rt, n));
79
+ }
80
+ return result;
81
+ }
82
+
83
+ void TalaDBHostObject::set(Runtime &, const PropNameID &, const Value &) {}
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // Property dispatch
87
+ // ---------------------------------------------------------------------------
88
+
89
+ Value TalaDBHostObject::get(Runtime &rt, const PropNameID &propName) {
90
+ auto name = propName.utf8(rt);
91
+
92
+ // ------------------------------------------------------------------
93
+ // insert(collection: string, doc: object): string
94
+ // ------------------------------------------------------------------
95
+ if (name == "insert") {
96
+ return Function::createFromHostFunction(
97
+ rt, PropNameID::forAscii(rt, "insert"), 2,
98
+ [this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
99
+ if (count < 2) throw JSError(rt, "insert requires 2 arguments");
100
+ auto col = args[0].getString(rt).utf8(rt);
101
+ auto docJson = stringify(rt, args[1]);
102
+ char *result = taladb_insert(db_, col.c_str(), docJson.c_str());
103
+ if (!result) throw JSError(rt, "taladb_insert failed");
104
+ std::string id(result);
105
+ taladb_free_string(result);
106
+ return String::createFromUtf8(rt, id);
107
+ });
108
+ }
109
+
110
+ // ------------------------------------------------------------------
111
+ // insertMany(collection: string, docs: object[]): string[]
112
+ // ------------------------------------------------------------------
113
+ if (name == "insertMany") {
114
+ return Function::createFromHostFunction(
115
+ rt, PropNameID::forAscii(rt, "insertMany"), 2,
116
+ [this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
117
+ if (count < 2) throw JSError(rt, "insertMany requires 2 arguments");
118
+ auto col = args[0].getString(rt).utf8(rt);
119
+ auto docsJson = stringify(rt, args[1]);
120
+ char *result = taladb_insert_many(db_, col.c_str(), docsJson.c_str());
121
+ if (!result) throw JSError(rt, "taladb_insert_many failed");
122
+ std::string json(result);
123
+ taladb_free_string(result);
124
+ return parse(rt, json);
125
+ });
126
+ }
127
+
128
+ // ------------------------------------------------------------------
129
+ // find(collection: string, filter: object | null): object[]
130
+ // ------------------------------------------------------------------
131
+ if (name == "find") {
132
+ return Function::createFromHostFunction(
133
+ rt, PropNameID::forAscii(rt, "find"), 2,
134
+ [this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
135
+ if (count < 1) throw JSError(rt, "find requires at least 1 argument");
136
+ auto col = args[0].getString(rt).utf8(rt);
137
+ auto filterJson = count > 1 ? valueToFilterJson(rt, args[1]) : "{}";
138
+ char *result = taladb_find(db_, col.c_str(), filterJson.c_str());
139
+ if (!result) throw JSError(rt, "taladb_find failed");
140
+ std::string json(result);
141
+ taladb_free_string(result);
142
+ return parse(rt, json);
143
+ });
144
+ }
145
+
146
+ // ------------------------------------------------------------------
147
+ // findOne(collection: string, filter: object | null): object | null
148
+ // ------------------------------------------------------------------
149
+ if (name == "findOne") {
150
+ return Function::createFromHostFunction(
151
+ rt, PropNameID::forAscii(rt, "findOne"), 2,
152
+ [this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
153
+ if (count < 1) throw JSError(rt, "findOne requires at least 1 argument");
154
+ auto col = args[0].getString(rt).utf8(rt);
155
+ auto filterJson = count > 1 ? valueToFilterJson(rt, args[1]) : "{}";
156
+ char *result = taladb_find_one(db_, col.c_str(), filterJson.c_str());
157
+ if (!result) throw JSError(rt, "taladb_find_one failed");
158
+ std::string json(result);
159
+ taladb_free_string(result);
160
+ return parse(rt, json);
161
+ });
162
+ }
163
+
164
+ // ------------------------------------------------------------------
165
+ // updateOne(collection, filter, update): boolean
166
+ // ------------------------------------------------------------------
167
+ if (name == "updateOne") {
168
+ return Function::createFromHostFunction(
169
+ rt, PropNameID::forAscii(rt, "updateOne"), 3,
170
+ [this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
171
+ if (count < 3) throw JSError(rt, "updateOne requires 3 arguments");
172
+ auto col = args[0].getString(rt).utf8(rt);
173
+ auto filterJson = stringify(rt, args[1]);
174
+ auto updateJson = stringify(rt, args[2]);
175
+ int32_t res = taladb_update_one(
176
+ db_, col.c_str(), filterJson.c_str(), updateJson.c_str());
177
+ if (res < 0) throw JSError(rt, "taladb_update_one failed");
178
+ return Value(res == 1);
179
+ });
180
+ }
181
+
182
+ // ------------------------------------------------------------------
183
+ // updateMany(collection, filter, update): number
184
+ // ------------------------------------------------------------------
185
+ if (name == "updateMany") {
186
+ return Function::createFromHostFunction(
187
+ rt, PropNameID::forAscii(rt, "updateMany"), 3,
188
+ [this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
189
+ if (count < 3) throw JSError(rt, "updateMany requires 3 arguments");
190
+ auto col = args[0].getString(rt).utf8(rt);
191
+ auto filterJson = stringify(rt, args[1]);
192
+ auto updateJson = stringify(rt, args[2]);
193
+ int32_t res = taladb_update_many(
194
+ db_, col.c_str(), filterJson.c_str(), updateJson.c_str());
195
+ if (res < 0) throw JSError(rt, "taladb_update_many failed");
196
+ return Value(static_cast<double>(res));
197
+ });
198
+ }
199
+
200
+ // ------------------------------------------------------------------
201
+ // deleteOne(collection, filter): boolean
202
+ // ------------------------------------------------------------------
203
+ if (name == "deleteOne") {
204
+ return Function::createFromHostFunction(
205
+ rt, PropNameID::forAscii(rt, "deleteOne"), 2,
206
+ [this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
207
+ if (count < 2) throw JSError(rt, "deleteOne requires 2 arguments");
208
+ auto col = args[0].getString(rt).utf8(rt);
209
+ auto filterJson = stringify(rt, args[1]);
210
+ int32_t res = taladb_delete_one(db_, col.c_str(), filterJson.c_str());
211
+ if (res < 0) throw JSError(rt, "taladb_delete_one failed");
212
+ return Value(res == 1);
213
+ });
214
+ }
215
+
216
+ // ------------------------------------------------------------------
217
+ // deleteMany(collection, filter): number
218
+ // ------------------------------------------------------------------
219
+ if (name == "deleteMany") {
220
+ return Function::createFromHostFunction(
221
+ rt, PropNameID::forAscii(rt, "deleteMany"), 2,
222
+ [this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
223
+ if (count < 2) throw JSError(rt, "deleteMany requires 2 arguments");
224
+ auto col = args[0].getString(rt).utf8(rt);
225
+ auto filterJson = stringify(rt, args[1]);
226
+ int32_t res = taladb_delete_many(db_, col.c_str(), filterJson.c_str());
227
+ if (res < 0) throw JSError(rt, "taladb_delete_many failed");
228
+ return Value(static_cast<double>(res));
229
+ });
230
+ }
231
+
232
+ // ------------------------------------------------------------------
233
+ // count(collection, filter): number
234
+ // ------------------------------------------------------------------
235
+ if (name == "count") {
236
+ return Function::createFromHostFunction(
237
+ rt, PropNameID::forAscii(rt, "count"), 2,
238
+ [this](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
239
+ if (count < 1) throw JSError(rt, "count requires at least 1 argument");
240
+ auto col = args[0].getString(rt).utf8(rt);
241
+ auto filterJson = count > 1 ? valueToFilterJson(rt, args[1]) : "{}";
242
+ int32_t res = taladb_count(db_, col.c_str(), filterJson.c_str());
243
+ if (res < 0) throw JSError(rt, "taladb_count failed");
244
+ return Value(static_cast<double>(res));
245
+ });
246
+ }
247
+
248
+ // ------------------------------------------------------------------
249
+ // createIndex / dropIndex / createFtsIndex / dropFtsIndex
250
+ // ------------------------------------------------------------------
251
+ if (name == "createIndex" || name == "dropIndex" ||
252
+ name == "createFtsIndex" || name == "dropFtsIndex") {
253
+ return Function::createFromHostFunction(
254
+ rt, PropNameID::forUtf8(rt, name), 2,
255
+ [this, name](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
256
+ if (count < 2) throw JSError(rt, (name + " requires 2 arguments").c_str());
257
+ auto col = args[0].getString(rt).utf8(rt);
258
+ auto field = args[1].getString(rt).utf8(rt);
259
+ if (name == "createIndex") taladb_create_index (db_, col.c_str(), field.c_str());
260
+ else if (name == "dropIndex") taladb_drop_index (db_, col.c_str(), field.c_str());
261
+ else if (name == "createFtsIndex") taladb_create_fts_index(db_, col.c_str(), field.c_str());
262
+ else taladb_drop_fts_index (db_, col.c_str(), field.c_str());
263
+ return Value::undefined();
264
+ });
265
+ }
266
+
267
+ // ------------------------------------------------------------------
268
+ // close(): void (synchronous — the destructor does the real work)
269
+ // ------------------------------------------------------------------
270
+ if (name == "close") {
271
+ return Function::createFromHostFunction(
272
+ rt, PropNameID::forAscii(rt, "close"), 0,
273
+ [this](Runtime &rt, const Value &, const Value *, size_t) -> Value {
274
+ if (db_) {
275
+ taladb_close(db_);
276
+ db_ = nullptr;
277
+ }
278
+ return Value::undefined();
279
+ });
280
+ }
281
+
282
+ return Value::undefined();
283
+ }
284
+
285
+ } // namespace taladb
@@ -0,0 +1,55 @@
1
+ #pragma once
2
+
3
+ #include <jsi/jsi.h>
4
+ #include <string>
5
+ #include "taladb.h"
6
+
7
+ namespace taladb {
8
+
9
+ /**
10
+ * TalaDBHostObject — JSI HostObject wrapping the Rust taladb-ffi C library.
11
+ *
12
+ * Installed into the JS runtime as a global:
13
+ * global.__TalaDB__ = <TalaDBHostObject instance>
14
+ *
15
+ * Every property access returns a JSI Function. All CRUD methods are
16
+ * synchronous (the Rust core does no async I/O); `initialize` and `close`
17
+ * are async only to conform to the TurboModule spec (they resolve immediately).
18
+ *
19
+ * JSON is used at the C boundary:
20
+ * JS object → JSON.stringify → C string → Rust → C string → JSON.parse → JS object
21
+ */
22
+ class TalaDBHostObject : public facebook::jsi::HostObject {
23
+ public:
24
+ explicit TalaDBHostObject(TalaDbHandle *db);
25
+ ~TalaDBHostObject() override;
26
+
27
+ facebook::jsi::Value get(facebook::jsi::Runtime &rt,
28
+ const facebook::jsi::PropNameID &name) override;
29
+
30
+ void set(facebook::jsi::Runtime &rt,
31
+ const facebook::jsi::PropNameID &name,
32
+ const facebook::jsi::Value &value) override;
33
+
34
+ std::vector<facebook::jsi::PropNameID>
35
+ getPropertyNames(facebook::jsi::Runtime &rt) override;
36
+
37
+ /** Install this object as global.__TalaDB__ in the given runtime. */
38
+ static void install(facebook::jsi::Runtime &rt, TalaDbHandle *db);
39
+
40
+ private:
41
+ TalaDbHandle *db_;
42
+
43
+ // JSON helpers
44
+ static std::string stringify(facebook::jsi::Runtime &rt,
45
+ const facebook::jsi::Value &val);
46
+ static facebook::jsi::Value parse(facebook::jsi::Runtime &rt,
47
+ const std::string &json);
48
+
49
+ // Convenience: convert a nullable JSI Value to a JSON C-string.
50
+ // Returns "{}" when the value is null/undefined.
51
+ static std::string valueToFilterJson(facebook::jsi::Runtime &rt,
52
+ const facebook::jsi::Value &val);
53
+ };
54
+
55
+ } // namespace taladb
@@ -0,0 +1,45 @@
1
+ /**
2
+ * TalaDB JNI glue — Android only.
3
+ *
4
+ * Exposes `nativeInstall(jsContextNativePtr, dbPath)` to Kotlin so that
5
+ * `TalaDBModule.kt` can install the JSI HostObject from the JS thread.
6
+ *
7
+ * The function signature must match the Kotlin `external fun` declaration:
8
+ * package : com.taladb
9
+ * class : TalaDBModule
10
+ * method : nativeInstall(Long, String)
11
+ *
12
+ * CMakeLists.txt compiles this file together with TalaDBHostObject.cpp.
13
+ */
14
+
15
+ #include <jni.h>
16
+ #include <jsi/jsi.h>
17
+ #include <string>
18
+
19
+ #include "TalaDBHostObject.h"
20
+ #include "taladb.h"
21
+
22
+ using namespace facebook::jsi;
23
+
24
+ extern "C" JNIEXPORT void JNICALL
25
+ Java_com_taladb_TalaDBModule_nativeInstall(
26
+ JNIEnv *env,
27
+ jobject /* thiz */,
28
+ jlong jsContextNativePtr,
29
+ jstring dbPathJ)
30
+ {
31
+ // Resolve db path
32
+ const char *dbPathC = env->GetStringUTFChars(dbPathJ, nullptr);
33
+ std::string dbPath(dbPathC);
34
+ env->ReleaseStringUTFChars(dbPathJ, dbPathC);
35
+
36
+ // Open the Rust database
37
+ TalaDbHandle *db = taladb_open(dbPath.c_str());
38
+ if (!db) return; // failed to open — JS will see no __TalaDB__ global
39
+
40
+ // Get the JSI runtime from the pointer passed by RN internals
41
+ auto &rt = *reinterpret_cast<Runtime *>(jsContextNativePtr);
42
+
43
+ // Install the JSI HostObject as global.__TalaDB__
44
+ taladb::TalaDBHostObject::install(rt, db);
45
+ }
package/cpp/taladb.h ADDED
@@ -0,0 +1,143 @@
1
+ #pragma once
2
+ #ifndef TALADB_FFI_H
3
+ #define TALADB_FFI_H
4
+
5
+ /*
6
+ * TalaDB C FFI header.
7
+ *
8
+ * This file is the stable C interface between the Rust taladb-ffi crate and
9
+ * the C++ JSI HostObject. It is kept in sync with rust/src/lib.rs manually
10
+ * (or regenerated with cbindgen — see rust/cbindgen.toml).
11
+ *
12
+ * Ownership rules
13
+ * ---------------
14
+ * - Strings IN : caller-owned, UTF-8, null-terminated.
15
+ * - Strings OUT : heap-allocated by Rust; caller must free with
16
+ * taladb_free_string().
17
+ * - Handles : allocated by taladb_open(); freed by taladb_close().
18
+ * - Errors : string functions return NULL; integer functions return -1.
19
+ */
20
+
21
+ #include <stdint.h>
22
+ #include <stdlib.h>
23
+
24
+ #ifdef __cplusplus
25
+ extern "C" {
26
+ #endif
27
+
28
+ /* -------------------------------------------------------------------------
29
+ * Opaque database handle
30
+ * ---------------------------------------------------------------------- */
31
+ typedef struct TalaDbHandle TalaDbHandle;
32
+
33
+ /* -------------------------------------------------------------------------
34
+ * Lifecycle
35
+ * ---------------------------------------------------------------------- */
36
+
37
+ /** Open (or create) a database at the given file-system path. */
38
+ TalaDbHandle *taladb_open(const char *path);
39
+
40
+ /** Flush and close the database, freeing the handle. */
41
+ void taladb_close(TalaDbHandle *handle);
42
+
43
+ /** Free a C string returned by any taladb_* function. */
44
+ void taladb_free_string(char *s);
45
+
46
+ /* -------------------------------------------------------------------------
47
+ * Insert
48
+ * ---------------------------------------------------------------------- */
49
+
50
+ /**
51
+ * Insert a document (JSON object).
52
+ * Returns the new document's ULID as a C string, or NULL on error.
53
+ * Caller must free with taladb_free_string().
54
+ */
55
+ char *taladb_insert(TalaDbHandle *handle,
56
+ const char *collection,
57
+ const char *doc_json);
58
+
59
+ /**
60
+ * Insert multiple documents (JSON array of objects).
61
+ * Returns a JSON array of ULID strings, or NULL on error.
62
+ * Caller must free with taladb_free_string().
63
+ */
64
+ char *taladb_insert_many(TalaDbHandle *handle,
65
+ const char *collection,
66
+ const char *docs_json);
67
+
68
+ /* -------------------------------------------------------------------------
69
+ * Find
70
+ * ---------------------------------------------------------------------- */
71
+
72
+ /**
73
+ * Find all documents matching filter_json.
74
+ * Pass "{}" or "null" to match all.
75
+ * Returns a JSON array string, or NULL on error.
76
+ * Caller must free with taladb_free_string().
77
+ */
78
+ char *taladb_find(TalaDbHandle *handle,
79
+ const char *collection,
80
+ const char *filter_json);
81
+
82
+ /**
83
+ * Find the first document matching filter_json.
84
+ * Returns a JSON object string, or the string "null" if not found.
85
+ * Caller must free with taladb_free_string().
86
+ */
87
+ char *taladb_find_one(TalaDbHandle *handle,
88
+ const char *collection,
89
+ const char *filter_json);
90
+
91
+ /* -------------------------------------------------------------------------
92
+ * Update
93
+ * ---------------------------------------------------------------------- */
94
+
95
+ /** Update the first matching document. Returns 1 updated, 0 not found, -1 error. */
96
+ int32_t taladb_update_one(TalaDbHandle *handle,
97
+ const char *collection,
98
+ const char *filter_json,
99
+ const char *update_json);
100
+
101
+ /** Update all matching documents. Returns count updated, or -1 on error. */
102
+ int32_t taladb_update_many(TalaDbHandle *handle,
103
+ const char *collection,
104
+ const char *filter_json,
105
+ const char *update_json);
106
+
107
+ /* -------------------------------------------------------------------------
108
+ * Delete
109
+ * ---------------------------------------------------------------------- */
110
+
111
+ /** Delete the first matching document. Returns 1 deleted, 0 not found, -1 error. */
112
+ int32_t taladb_delete_one(TalaDbHandle *handle,
113
+ const char *collection,
114
+ const char *filter_json);
115
+
116
+ /** Delete all matching documents. Returns count deleted, or -1 on error. */
117
+ int32_t taladb_delete_many(TalaDbHandle *handle,
118
+ const char *collection,
119
+ const char *filter_json);
120
+
121
+ /* -------------------------------------------------------------------------
122
+ * Count
123
+ * ---------------------------------------------------------------------- */
124
+
125
+ /** Count documents matching filter_json. Returns count, or -1 on error. */
126
+ int32_t taladb_count(TalaDbHandle *handle,
127
+ const char *collection,
128
+ const char *filter_json);
129
+
130
+ /* -------------------------------------------------------------------------
131
+ * Index management
132
+ * ---------------------------------------------------------------------- */
133
+
134
+ void taladb_create_index (TalaDbHandle *handle, const char *collection, const char *field);
135
+ void taladb_drop_index (TalaDbHandle *handle, const char *collection, const char *field);
136
+ void taladb_create_fts_index(TalaDbHandle *handle, const char *collection, const char *field);
137
+ void taladb_drop_fts_index (TalaDbHandle *handle, const char *collection, const char *field);
138
+
139
+ #ifdef __cplusplus
140
+ } /* extern "C" */
141
+ #endif
142
+
143
+ #endif /* TALADB_FFI_H */
package/ios/TalaDB.mm ADDED
@@ -0,0 +1,146 @@
1
+ /**
2
+ * TalaDB React Native — iOS TurboModule + JSI HostObject installer.
3
+ *
4
+ * Build setup (Xcode / CocoaPods)
5
+ * --------------------------------
6
+ * 1. Run `cargo build --target aarch64-apple-ios --release` (device) and
7
+ * `cargo build --target x86_64-apple-ios --release` (simulator), then
8
+ * `lipo` them into a universal `libtaladb_ffi.a`.
9
+ * 2. The podspec links the fat archive and adds `cpp/` to the header search
10
+ * paths — both are handled automatically when using the podspec.
11
+ *
12
+ * Runtime flow
13
+ * ------------
14
+ * AppDelegate calls `[TalaDB installInRuntime:rt dbPath:path]` once the
15
+ * React bridge is ready. This opens the Rust database and installs
16
+ * `global.__TalaDB__` as a JSI HostObject.
17
+ *
18
+ * NativeTalaDB.ts routes all synchronous CRUD calls directly through
19
+ * `global.__TalaDB__` — the TurboModule stubs below satisfy Codegen but
20
+ * are never invoked at runtime.
21
+ */
22
+
23
+ #import <React/RCTBridgeModule.h>
24
+ #import <ReactCommon/RCTTurboModule.h>
25
+ #import <ReactCommon/CallInvoker.h>
26
+ #import <jsi/jsi.h>
27
+ #import <React/RCTBridge+Private.h>
28
+
29
+ #include "../cpp/TalaDBHostObject.h"
30
+ #include "../cpp/taladb.h"
31
+
32
+ #import <Foundation/Foundation.h>
33
+
34
+ using namespace facebook::jsi;
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Global handle — one database per process
38
+ // ---------------------------------------------------------------------------
39
+
40
+ static TalaDbHandle *gHandle = nullptr;
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // TalaDB — Obj-C TurboModule
44
+ // ---------------------------------------------------------------------------
45
+
46
+ @interface TalaDB : NSObject <RCTBridgeModule, RCTTurboModule>
47
+ @end
48
+
49
+ @implementation TalaDB
50
+
51
+ RCT_EXPORT_MODULE(TalaDB)
52
+
53
+ // ---- Class method: open DB and install the JSI HostObject ----------------
54
+
55
+ + (void)installInRuntime:(facebook::jsi::Runtime &)rt
56
+ dbPath:(NSString *)path {
57
+ if (gHandle) {
58
+ taladb_close(gHandle);
59
+ gHandle = nullptr;
60
+ }
61
+
62
+ gHandle = taladb_open(path.UTF8String);
63
+ if (!gHandle) {
64
+ NSLog(@"[TalaDB] Failed to open database at %@", path);
65
+ return;
66
+ }
67
+
68
+ taladb::TalaDBHostObject::install(rt, gHandle);
69
+ NSLog(@"[TalaDB] Installed JSI HostObject — db: %@", path);
70
+ }
71
+
72
+ // ---- initialize(dbName) → Promise<void> ---------------------------------
73
+
74
+ RCT_EXPORT_METHOD(initialize:(NSString *)dbName
75
+ resolve:(RCTPromiseResolveBlock)resolve
76
+ reject:(RCTPromiseRejectBlock)reject) {
77
+ @try {
78
+ NSString *docs = [NSSearchPathForDirectoriesInDomains(
79
+ NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
80
+ NSString *dbPath = [docs stringByAppendingPathComponent:dbName];
81
+
82
+ RCTCxxBridge *bridge = (RCTCxxBridge *)[RCTBridge currentBridge];
83
+ if (!bridge || !bridge.runtime) {
84
+ reject(@"TALADB_NO_BRIDGE", @"JSI bridge not available", nil);
85
+ return;
86
+ }
87
+
88
+ // Install the HostObject on the JS thread
89
+ bridge.jsCallInvoker->invokeAsync([bridge, dbPath]() {
90
+ auto &rt = *(Runtime *)bridge.runtime;
91
+ [TalaDB installInRuntime:rt dbPath:dbPath];
92
+ });
93
+
94
+ resolve(nil);
95
+ } @catch (NSException *ex) {
96
+ reject(@"TALADB_INIT_ERROR", ex.reason, nil);
97
+ }
98
+ }
99
+
100
+ // ---- close() → Promise<void> --------------------------------------------
101
+
102
+ RCT_EXPORT_METHOD(close:(RCTPromiseResolveBlock)resolve
103
+ reject:(RCTPromiseRejectBlock)reject) {
104
+ if (gHandle) {
105
+ taladb_close(gHandle);
106
+ gHandle = nullptr;
107
+ }
108
+ resolve(nil);
109
+ }
110
+
111
+ // ---- Synchronous stubs — all real work goes through the JSI HostObject ---
112
+ // These exist only to satisfy the TurboModule Codegen spec (NativeTalaDB.ts).
113
+
114
+ RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, insert:(NSString *)collection doc:(NSDictionary *)doc) {
115
+ return nil;
116
+ }
117
+ RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSArray *, insertMany:(NSString *)collection docs:(NSArray *)docs) {
118
+ return nil;
119
+ }
120
+ RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSArray *, find:(NSString *)collection filter:(NSDictionary *)filter) {
121
+ return nil;
122
+ }
123
+ RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, findOne:(NSString *)collection filter:(NSDictionary *)filter) {
124
+ return nil;
125
+ }
126
+ RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(BOOL, updateOne:(NSString *)collection filter:(NSDictionary *)filter update:(NSDictionary *)update) {
127
+ return NO;
128
+ }
129
+ RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(double, updateMany:(NSString *)collection filter:(NSDictionary *)filter update:(NSDictionary *)update) {
130
+ return 0;
131
+ }
132
+ RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(BOOL, deleteOne:(NSString *)collection filter:(NSDictionary *)filter) {
133
+ return NO;
134
+ }
135
+ RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(double, deleteMany:(NSString *)collection filter:(NSDictionary *)filter) {
136
+ return 0;
137
+ }
138
+ RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(double, count:(NSString *)collection filter:(NSDictionary *)filter) {
139
+ return 0;
140
+ }
141
+ RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(void, createIndex:(NSString *)collection field:(NSString *)field) {}
142
+ RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(void, dropIndex:(NSString *)collection field:(NSString *)field) {}
143
+ RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(void, createFtsIndex:(NSString *)collection field:(NSString *)field) {}
144
+ RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(void, dropFtsIndex:(NSString *)collection field:(NSString *)field) {}
145
+
146
+ @end
Binary file
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@taladb/react-native",
3
+ "version": "0.1.0",
4
+ "description": "TalaDB React Native module — JSI HostObject for iOS and Android",
5
+ "main": "src/index",
6
+ "types": "src/index.d.ts",
7
+ "files": [
8
+ "src/",
9
+ "cpp/",
10
+ "ios/",
11
+ "android/",
12
+ "taladb-react-native.podspec"
13
+ ],
14
+ "peerDependencies": {
15
+ "react-native": ">=0.73.0"
16
+ },
17
+ "keywords": [
18
+ "database",
19
+ "local-first",
20
+ "react-native",
21
+ "jsi",
22
+ "offline"
23
+ ],
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/thinkgrid-labs/taladb.git",
27
+ "directory": "packages/taladb-react-native"
28
+ },
29
+ "homepage": "https://thinkgrid-labs.github.io/taladb/guide/react-native",
30
+ "bugs": {
31
+ "url": "https://github.com/thinkgrid-labs/taladb/issues"
32
+ },
33
+ "engines": {
34
+ "node": ">=18"
35
+ },
36
+ "license": "MIT",
37
+ "devDependencies": {
38
+ "typescript": "^5.9.3",
39
+ "react-native": "^0.83.0"
40
+ },
41
+ "scripts": {
42
+ "typecheck": "tsc --noEmit",
43
+ "build:ios": "sh scripts/build-ios.sh",
44
+ "build:android": "sh scripts/build-android.sh",
45
+ "build:cbindgen": "cbindgen --config rust/cbindgen.toml --crate taladb-ffi --output cpp/taladb.h"
46
+ }
47
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * TalaDB React Native — TurboModule spec.
3
+ *
4
+ * This file is the Codegen source. It defines the native interface that
5
+ * both the iOS JSI HostObject (TalaDB.mm) and the Android JNI bridge
6
+ * (TalaDBModule.kt) must implement.
7
+ */
8
+ import type { TurboModule } from 'react-native';
9
+ import { TurboModuleRegistry } from 'react-native';
10
+
11
+ export interface Spec extends TurboModule {
12
+ /**
13
+ * Open (or create) a TalaDB database file at the platform default path.
14
+ * Must be called once at app startup before using `collection()`.
15
+ */
16
+ initialize(dbName: string): Promise<void>;
17
+
18
+ /** Close the database and flush all pending writes. */
19
+ close(): Promise<void>;
20
+
21
+ // ------------------------------------------------------------------
22
+ // Collection CRUD — all methods are synchronous via JSI
23
+ // ------------------------------------------------------------------
24
+
25
+ /** Insert a document. Returns the ULID string id. */
26
+ insert(collection: string, doc: Object): string;
27
+
28
+ /** Insert multiple documents. Returns an array of ULID string ids. */
29
+ insertMany(collection: string, docs: Object[]): string[];
30
+
31
+ /** Find documents matching the filter. */
32
+ find(collection: string, filter: Object | null): Object[];
33
+
34
+ /** Find a single document or null. */
35
+ findOne(collection: string, filter: Object | null): Object | null;
36
+
37
+ /** Update the first matching document. Returns true if updated. */
38
+ updateOne(collection: string, filter: Object, update: Object): boolean;
39
+
40
+ /** Update all matching documents. Returns the count updated. */
41
+ updateMany(collection: string, filter: Object, update: Object): number;
42
+
43
+ /** Delete the first matching document. Returns true if deleted. */
44
+ deleteOne(collection: string, filter: Object): boolean;
45
+
46
+ /** Delete all matching documents. Returns the count deleted. */
47
+ deleteMany(collection: string, filter: Object): number;
48
+
49
+ /** Count documents matching the filter. */
50
+ count(collection: string, filter: Object | null): number;
51
+
52
+ // ------------------------------------------------------------------
53
+ // Index management
54
+ // ------------------------------------------------------------------
55
+
56
+ createIndex(collection: string, field: string): void;
57
+ dropIndex(collection: string, field: string): void;
58
+ createFtsIndex(collection: string, field: string): void;
59
+ dropFtsIndex(collection: string, field: string): void;
60
+ }
61
+
62
+ export default TurboModuleRegistry.getEnforcing<Spec>('TalaDB');
package/src/index.tsx ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * TalaDB React Native — public JS API.
3
+ *
4
+ * Usage:
5
+ * ```ts
6
+ * import { TalaDBModule, openDB } from '@taladb/react-native';
7
+ *
8
+ * // In App.tsx / index.js (once, at startup)
9
+ * await TalaDBModule.initialize('myapp.db');
10
+ *
11
+ * // Anywhere in the app — same API as browser
12
+ * const db = openDB('myapp.db');
13
+ * const users = db.collection<User>('users');
14
+ * const id = users.insert({ name: 'Alice', age: 30 });
15
+ * ```
16
+ *
17
+ * All operations are **synchronous** via JSI — no async/await needed
18
+ * after initialization.
19
+ */
20
+ import NativeTalaDB from './NativeTalaDB';
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Module-level helpers
24
+ // ---------------------------------------------------------------------------
25
+
26
+ export const TalaDBModule = {
27
+ /** Open (or create) the database. Call once at app startup. */
28
+ initialize: (dbName: string) => NativeTalaDB.initialize(dbName),
29
+ /** Close the database gracefully. */
30
+ close: () => NativeTalaDB.close(),
31
+ };
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Collection handle
35
+ // ---------------------------------------------------------------------------
36
+
37
+ export interface Document {
38
+ _id?: string;
39
+ [key: string]: unknown;
40
+ }
41
+
42
+ export type Filter = Record<string, unknown>;
43
+ export type Update = Record<string, unknown>;
44
+
45
+ export interface Collection<T extends Document = Document> {
46
+ insert(doc: Omit<T, '_id'>): string;
47
+ insertMany(docs: Omit<T, '_id'>[]): string[];
48
+ find(filter?: Filter): T[];
49
+ findOne(filter: Filter): T | null;
50
+ updateOne(filter: Filter, update: Update): boolean;
51
+ updateMany(filter: Filter, update: Update): number;
52
+ deleteOne(filter: Filter): boolean;
53
+ deleteMany(filter: Filter): number;
54
+ count(filter?: Filter): number;
55
+ createIndex(field: string): void;
56
+ dropIndex(field: string): void;
57
+ createFtsIndex(field: string): void;
58
+ dropFtsIndex(field: string): void;
59
+ }
60
+
61
+ export interface DB {
62
+ collection<T extends Document = Document>(name: string): Collection<T>;
63
+ close(): Promise<void>;
64
+ }
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // openDB — synchronous DB handle (after initialize() has been called)
68
+ // ---------------------------------------------------------------------------
69
+
70
+ /**
71
+ * Get a synchronous DB handle for the given database name.
72
+ *
73
+ * `TalaDBModule.initialize(dbName)` **must** have been awaited before calling
74
+ * this function.
75
+ */
76
+ function collection<T extends Document>(colName: string): Collection<T> {
77
+ return {
78
+ insert: (doc) => NativeTalaDB.insert(colName, doc as Object),
79
+ insertMany: (docs) => NativeTalaDB.insertMany(colName, docs as Object[]),
80
+ find: (filter?) => NativeTalaDB.find(colName, filter ?? null) as T[],
81
+ findOne: (filter) => NativeTalaDB.findOne(colName, filter) as T | null,
82
+ updateOne: (filter, update) => NativeTalaDB.updateOne(colName, filter, update),
83
+ updateMany: (filter, update) => NativeTalaDB.updateMany(colName, filter, update),
84
+ deleteOne: (filter) => NativeTalaDB.deleteOne(colName, filter),
85
+ deleteMany: (filter) => NativeTalaDB.deleteMany(colName, filter),
86
+ count: (filter?) => NativeTalaDB.count(colName, filter ?? null),
87
+ createIndex: (field) => NativeTalaDB.createIndex(colName, field),
88
+ dropIndex: (field) => NativeTalaDB.dropIndex(colName, field),
89
+ createFtsIndex: (field) => NativeTalaDB.createFtsIndex(colName, field),
90
+ dropFtsIndex: (field) => NativeTalaDB.dropFtsIndex(colName, field),
91
+ };
92
+ }
93
+
94
+ export function openDB(_dbName: string): DB {
95
+ return {
96
+ collection,
97
+ close: () => NativeTalaDB.close(),
98
+ };
99
+ }
@@ -0,0 +1,50 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "taladb-react-native"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = "https://github.com/thinkgrid-labs/taladb"
10
+ s.license = package["license"]
11
+ s.authors = { "thinkgrid-labs" => "hello@thinkgrid.io" }
12
+
13
+ s.platforms = { :ios => "13.0" }
14
+ s.source = { :git => "https://github.com/thinkgrid-labs/taladb.git",
15
+ :tag => "v#{s.version}" }
16
+
17
+ # TypeScript / JS sources (not compiled by Xcode, just bundled)
18
+ s.source_files = "ios/**/*.{h,m,mm}", "cpp/**/*.{h,cpp}"
19
+
20
+ # ---------------------------------------------------------------------------
21
+ # Pre-built Rust static library
22
+ # ---------------------------------------------------------------------------
23
+ # Build with:
24
+ # cargo build --target aarch64-apple-ios --release (device)
25
+ # cargo build --target x86_64-apple-ios --release (simulator Intel)
26
+ # cargo build --target aarch64-apple-ios-sim --release (simulator Apple Silicon)
27
+ # lipo device + sim → ios/libtaladb_ffi.a (fat / xcframework)
28
+ #
29
+ # The podspec expects the lipo'd archive at ios/libtaladb_ffi.a.
30
+ s.vendored_libraries = "ios/libtaladb_ffi.a"
31
+
32
+ # ---------------------------------------------------------------------------
33
+ # Compiler settings
34
+ # ---------------------------------------------------------------------------
35
+ s.pod_target_xcconfig = {
36
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
37
+ "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
38
+ "HEADER_SEARCH_PATHS" => "$(PODS_ROOT)/Headers/Public/React-Core $(PODS_ROOT)/Headers/Public/React-RCTFabric",
39
+ "LIBRARY_SEARCH_PATHS" => "$(PODS_ROOT)/../ios",
40
+ # Suppress linker warnings from the Rust archive
41
+ "OTHER_LDFLAGS" => "-lc++ -lz",
42
+ }
43
+
44
+ # ---------------------------------------------------------------------------
45
+ # React Native dependencies
46
+ # ---------------------------------------------------------------------------
47
+ s.dependency "React-Core"
48
+ s.dependency "React-jsi"
49
+ s.dependency "ReactCommon/turbomodule/core"
50
+ end