React Native
TalaDB integrates with React Native through a JSI (JavaScript Interface) HostObject. Unlike a bridge-based module, JSI allows synchronous, zero-serialisation calls from JavaScript directly into the Rust engine — no JSON serialisation on the hot path, no async roundtrip for reads.
Architecture
React Native (JS thread)
│ JSI direct call (synchronous)
▼
TalaDBHostObject (C++ HostObject — cpp/TalaDBHostObject.cpp)
│ C FFI
▼
taladb-ffi (Rust cdylib — no_mangle extern "C")
│
▼
taladb-core (Rust) + redb (file in app Documents dir)The HostObject is installed into the JSI runtime once at app startup by the native TurboModule. After that, all JavaScript calls go directly through JSI without touching the bridge.
Status
Alpha — integration in progress
The React Native integration has a complete C FFI layer (taladb-ffi), C++ HostObject scaffold, and iOS / Android TurboModule stubs. Full end-to-end integration (Xcode build phases, Gradle AAR packaging) is in progress. The API documented here reflects the intended final shape.
Prerequisites
- React Native 0.73+ (New Architecture enabled)
- Expo SDK 50+ (if using Expo)
- Xcode 15+ (iOS)
- Android NDK r26+ (Android)
- Rust toolchain with iOS / Android targets installed:
rustup target add aarch64-apple-ios x86_64-apple-ios # iOS
rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android # AndroidInstallation
There are two ways to install TalaDB in a React Native project depending on whether you share code with other platforms.
Option A — Shared codebase (RN + Web or RN + Node.js)
pnpm add taladb @taladb/react-nativeUse openDB from taladb. The unified package detects React Native automatically and delegates to @taladb/react-native under the hood. All calls use a consistent async API — the same code compiles for browser and Node.js too:
import { openDB } from 'taladb'
const db = await openDB('myapp.db')
const users = db.collection('users')
await users.insert({ name: 'Alice' })Option B — Standalone (React Native only)
pnpm add @taladb/react-nativeImport directly from @taladb/react-native. Because calls go through JSI, they are synchronous — no await required after initialize:
import { TalaDBModule, openDB } from '@taladb/react-native'
// Call once at app startup
await TalaDBModule.initialize('myapp.db')
// After that — fully synchronous
const db = openDB('myapp.db')
const users = db.collection('users')
const id = users.insert({ name: 'Alice' }) // no await
const found = users.find({ name: 'Alice' }) // no awaitUse this option when you have no shared code requirements and want the simplest possible setup with one fewer dependency.
iOS
cd ios && pod installThe pod install step picks up @taladb/react-native.podspec, which includes the pre-compiled libtaladb.a universal static library and the C++ HostObject sources.
Android
The Gradle build system automatically links libtaladb.so for the supported ABIs (arm64-v8a, armeabi-v7a, x86_64).
Enabling the New Architecture
TalaDB's JSI integration requires the New Architecture. In android/gradle.properties:
newArchEnabled=trueIn ios/Podfile:
use_framework! :staticInitialising the database
Call TalaDBModule.initialize as early as possible in your app's entry point — before any component tries to use the database.
// App.tsx (or index.js)
import { TalaDBModule } from '@taladb/react-native'
await TalaDBModule.initialize('myapp.db')This call:
- Resolves the absolute path for
myapp.dbinside the app's Documents directory (iOS) or files directory (Android). - Opens (or creates) the redb database at that path.
- Installs the
__TalaDB__JSI HostObject into the JS runtime.
The database path is sandboxed to the app's private storage — no special permissions are required.
Using the database
After initialize, use openDB from taladb exactly as you would in a browser or Node.js app:
import { openDB } from 'taladb'
const db = await openDB('myapp.db')The taladb package detects React Native by the presence of globalThis.nativeCallSyncHook and routes calls through the JSI HostObject instead of WASM or the native module.
Full example
// App.tsx
import React, { useEffect, useState } from 'react'
import { View, Text, Button, FlatList } from 'react-native'
import { TalaDBModule } from '@taladb/react-native'
import { openDB, type Collection } from 'taladb'
interface Note {
_id?: string
text: string
createdAt: number
}
let notes: Collection<Note>
export default function App() {
const [items, setItems] = useState<Note[]>([])
useEffect(() => {
async function init() {
await TalaDBModule.initialize('notes.db')
const db = await openDB('notes.db')
notes = db.collection<Note>('notes')
await notes.createIndex('createdAt')
setItems(await notes.find())
}
init()
}, [])
async function addNote() {
await notes.insert({ text: `Note ${Date.now()}`, createdAt: Date.now() })
setItems(await notes.find())
}
return (
<View style={{ flex: 1, padding: 40 }}>
<Button title="Add Note" onPress={addNote} />
<FlatList
data={items}
keyExtractor={(item) => item._id!}
renderItem={({ item }) => <Text>{item.text}</Text>}
/>
</View>
)
}Vector search
On-device vector search is a natural fit for React Native — embeddings generated by a local ML model (Core ML on iOS, TensorFlow Lite on Android) can be stored and searched without any server.
Create a vector index
interface Document {
_id?: string
title: string
content: string
category: string
embedding: number[]
}
const docs = db.collection<Document>('docs')
// Call once at startup alongside other index setup
await docs.createVectorIndex('embedding', { dimensions: 384 })Insert with embedding
Compute the embedding with whatever on-device ML library your app uses, then pass it as a plain number[]:
// embedding comes from your on-device ML model
const embedding = await myModel.embed(content)
await docs.insert({ title, content, category: 'faq', embedding })Semantic search
const queryVec = await myModel.embed(userQuery)
const results = await docs.findNearest('embedding', queryVec, 5)
results.forEach(({ document, score }) => {
console.log(score.toFixed(3), document.title)
})Hybrid search
// Only search within the 'faq' category
const results = await docs.findNearest('embedding', queryVec, 5, {
category: 'faq',
})Full React Native example with vector search
// SearchScreen.tsx
import React, { useState } from 'react'
import { View, TextInput, FlatList, Text, StyleSheet } from 'react-native'
import { openDB, type VectorSearchResult } from 'taladb'
interface Article {
_id?: string
title: string
body: string
embedding: number[]
}
const db = await openDB('myapp.db')
const articles = db.collection<Article>('articles')
await articles.createVectorIndex('embedding', { dimensions: 384 })
export function SearchScreen() {
const [results, setResults] = useState<VectorSearchResult<Article>[]>([])
async function handleSearch(query: string) {
if (!query.trim()) return
const queryVec = await myModel.embed(query) // your on-device model
const hits = await articles.findNearest('embedding', queryVec, 10)
setResults(hits)
}
return (
<View style={styles.container}>
<TextInput
placeholder="Search..."
onChangeText={handleSearch}
style={styles.input}
/>
<FlatList
data={results}
keyExtractor={(item) => item.document._id!}
renderItem={({ item }) => (
<View style={styles.row}>
<Text style={styles.score}>{item.score.toFixed(2)}</Text>
<Text style={styles.title}>{item.document.title}</Text>
</View>
)}
/>
</View>
)
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 16 },
input: { borderWidth: 1, borderRadius: 8, padding: 12, marginBottom: 12 },
row: { flexDirection: 'row', gap: 8, paddingVertical: 8 },
score: { color: '#888', width: 40 },
title: { flex: 1 },
})Migrations
const db = await openDB('myapp.db', {
migrations: [
{
version: 1,
description: 'Create notes index',
up: async (db) => {
await db.collection('notes').createIndex('createdAt')
},
},
],
})Data persistence and location
| Platform | Location |
|---|---|
| iOS | NSDocumentDirectory (iCloud-excluded by default) |
| Android | Context.getFilesDir() (app-private, no permissions needed) |
Data is not backed up to iCloud or Google Drive by default. To enable backup, configure NSFileProtection (iOS) or Android Backup rules as appropriate for your app.
Exporting and restoring snapshots
const bytes = await db.exportSnapshot()
// Transfer bytes over your sync layer, then restore on another device:
const db2 = await Database.restoreFromSnapshot(bytes)Live queries
const handle = db.collection<Note>('notes').watch({})
async function streamUpdates() {
for await (const snapshot of handle) {
setItems(snapshot)
}
}
streamUpdates()Building the Rust libraries
iOS
cd packages/@taladb/react-native/rust
# Build for device and simulator
cargo build --release --target aarch64-apple-ios
cargo build --release --target x86_64-apple-ios
# Create a universal static library
lipo -create \
../../target/aarch64-apple-ios/release/libtaladb_ffi.a \
../../target/x86_64-apple-ios/release/libtaladb_ffi.a \
-output ios/libtaladb.aAndroid
cd packages/@taladb/react-native/rust
cargo build --release --target aarch64-linux-android
cargo build --release --target armv7-linux-androideabi
cargo build --release --target x86_64-linux-androidThe Gradle build picks up the .so files from android/src/main/jniLibs/.
Troubleshooting
__TalaDB__ JSI HostObject not found You called openDB before TalaDBModule.initialize completed. Move initialize to the earliest possible point in your app startup and await it before rendering any component that accesses the database.
New Architecture is not enabled Set newArchEnabled=true in android/gradle.properties and ensure use_framework! :static is in your ios/Podfile.
Rust build errors on iOS Make sure the iOS targets are installed: rustup target add aarch64-apple-ios x86_64-apple-ios and that Xcode's command-line tools are active: xcode-select --install.
