react-native-turbo-db
v1.5.0
Published
react-native-turbo-db is a high-performance JSI database and AsyncStorage alternative for React Native, featuring encrypted storage, WAL persistence, B+tree indexing and offline-first local database support.
Downloads
4
Maintainers
Readme
react-native-turbo-db is a high-performance JSI database and AsyncStorage alternative for React Native, featuring encrypted storage, WAL persistence, B+tree indexing, and offline-first local database support.
⚡ What's New in v1.5.0 (Security Release)
TurboDB v1.5.0 is a dedicated hardening release focused on encryption correctness and hardware-backed security:
- 🔐 XChaCha20-Poly1305 Encryption — All data at rest is encrypted via
libsodium. TheSecureCryptoContextclass now correctly wires through every read/write path in the C++ engine. - 🔑 Encryption Key Rotation —
rotateEncryptionKeyAsync(newKey)re-encrypts all records in a single atomic pass without downtime. - 🛡️ Hardware Secure Enclave —
setSecureItemAsync/getSecureItemAsyncnow use the device Keystore (Android) and Secure Enclave (iOS) for master-key storage. - 🐛 Critical C++ Fix — Resolved incorrect
SodiumCryptoContextinstantiation that silently disabled encryption in theDBEngineconstructor. - ✅ Android Build Stabilised — Removed stale
calculate_crc32dependency; repair logic now delegates cleanly toPersistentBPlusTree::repair().
[!NOTE] Built on v1.4.0 Reactive Sync (Live Queries, Real Sync Engine, Correct Compaction)
🚀 Quick Start
Installation
npm install react-native-turbo-db
cd ios && pod installSetup
Android
Ensure newArchEnabled=true in your android/gradle.properties:
newArchEnabled=trueiOS
No additional configuration required. The pod installs automatically.
📖 Usage
Basic Initialization
[!IMPORTANT] Always use absolute paths from
TurboDB.getDocumentsDirectory().
import { TurboDB } from 'react-native-turbo-db';
const initDB = async () => {
// ⚠️ Important for New Architecture (Bridgeless / Fabric):
// A short delay before the first JSI call ensures native modules are fully wired up
await new Promise((r) => setTimeout(r, 100));
const docsDir = TurboDB.getDocumentsDirectory();
const dbPath = `${docsDir}/myapp.db`;
// Initialize with async factory (recommended)
const db = await TurboDB.create(dbPath, 20 * 1024 * 1024, {
syncEnabled: true,
});
return db;
};Synchronous Operations (Fast Path)
// Write (instant)
db.set('user:1', { name: 'Alice', role: 'admin' });
// Read (instant)
const user = db.get('user:1');
// Batch write
db.setMulti({
key1: 'value1',
key2: { data: 'object' },
});
// Check existence & Delete
if (db.has('user:1')) {
db.remove('user:1');
}Asynchronous Operations (Background Thread)
// For large data, use async to avoid blocking UI
await db.setAsync('largeData', hugePayload);
const data = await db.getAsync('largeData');
// Batch async write (10x faster with WAL batching)
await db.setMultiAsync(largeBatchObject);⚡ Live Queries & Reactive Sync (v1.4.0)
TurboDB now supports native real-time subscriptions. The UI will instantly update even if writes happen on a C++ background thread!
import { useEffect, useState } from 'react';
function ChatApp({ db }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
// Subscribe to all keys starting with 'msg:'
const unsubscribe = db.watchPrefix('msg:', (results) => {
setMessages(results.map(r => r.value));
});
return () => unsubscribe();
}, [db]);
// Calling setAsync here triggers the watchPrefix callback automatically!
const send = async (text) => db.setAsync(`msg:${Date.now()}`, { text });
}Advanced Queries
// Range query & Prefix search
const results = db.rangeQuery('user_a', 'user_z');
const items = db.getByPrefix('user_');
// Metadata & Cleanup
const allKeys = db.getAllKeys();
await db.deleteAllAsync();Initialization
| Method | Description |
| :------------------------------------ | :--------------------------------------------------- |
| TurboDB.create(path, size, options) | Async factory. Returns Promise<TurboDB>. |
| TurboDB.getDocumentsDirectory() | Returns absolute path for storage. |
| TurboDB.install() | Installs native JSI bindings (called automatically). |
Synchronous (Blocking)
| Method | Description |
| :----------------------- | :---------------------------------------------- |
| get(key) | Returns parsed object/primitive or undefined. |
| set(key, val) | Writes to storage. Returns boolean. |
| has(key) | Checks if key exists. |
| remove(key) | Deletes a record. |
| setMulti(obj) | Atomic batch insert. |
| getMultiple(keys) | Batch retrieval. |
| rangeQuery(start, end) | Lexicographical range fetch. |
| getByPrefix(prefix) | Fetch all keys starting with prefix. |
| deleteAll() | Synchronous database wipe. |
| getAllKeys() | Returns all keys in storage. |
Asynchronous (Worker Thread)
| Method | Description |
| :---------------------------- | :---------------------------------------------------------- |
| getAsync(key) | Background read. Returns Promise. |
| setAsync(key, val) | Background write. Returns Promise<boolean>. |
| setMultiAsync(obj) | 10x Faster WAL Batching. Atomic background batch write. |
| removeAsync(key) | Background delete. |
| deleteAllAsync() | Fast-path wipe for large datasets. |
| rangeQueryAsync(start, end) | Background range fetch. |
| getAllKeysAsync() | Background get all keys. |
Extended API (v1.4.0 / v1.5.0)
| Method | Description |
| :----------------------------------------- | :----------------------------------------------------------------------- |
| watchKey(key, cb) | Reactive key watcher — fires immediately, returns unsubscribe |
| watchPrefix(prefix, cb, opts?) | Reactive prefix query with optional debounce |
| watchQuery(fn, cb, opts?) | Reactive arbitrary async query with debounce |
| compactAsync() | Native compaction with fragmentation guard (>30%) |
| setWithTTLAsync(key, val, ttlMs) | Native TTL — durable expiry via C++ sidecar key |
| cleanupExpiredAsync() | Native sweep of all expired TTL keys. Returns count. |
| getByPrefixAsync(prefix) | Native B+Tree prefix traversal |
| regexSearchAsync(pattern) | Regex key filter using std::regex |
| exportAsync() | Full DB snapshot as JSON (native traversal) |
| importAsync(data) | Bulk insert from JSON. Returns record count. |
| setBlobAsync(key, base64) | Store raw binary data >1MB |
| getBlobAsync(key) | Retrieve raw binary as base64 |
| compareAndSet(key, expected, next) | Atomic compare-and-set |
| merge(key, partial) | Shallow-merge into existing object |
| setSecureItemAsync(key, val) | v1.5.0 Hardware Secure Enclave / Android Keystore storage |
| getSecureItemAsync(key) | v1.5.0 Retrieve from Secure Enclave / Android Keystore |
| rotateEncryptionKeyAsync(newKey) | v1.5.0 Atomic key rotation — re-encrypts all records in one pass |
| for await (const key of db.streamKeys()) | Async key streaming for large datasets |
Internal Architecture
graph LR
JS[JavaScript Layer] -- JSI (Zero Bridge) --> CPP[C++ TurboDB Engine]
CPP -- mmap --> File[(myapp.db)]
CPP -- atomic --> WAL[Write-Ahead Log]
WAL -- checkpoint --> File
CPP -- cache --> LRU[JSI LRU Cache]TurboDB utilizes a custom B+Tree Index on top of a Memory-Mapped (mmap) file, secured by a Write-Ahead Log (WAL) for ACID compliance. JSI allows direct communication between C++ and JavaScript, eliminating bridge overhead.
SyncManager (Offline-First)
Built-in synchronization for remote backends:
import { SyncManager } from 'react-native-turbo-db';
const syncManager = new SyncManager(
db,
{
pullChanges: async (lastClock) =>
fetch(`/api/sync?since=${lastClock}`).then((r) => r.json()),
pushChanges: async (changes) =>
fetch('/api/sync', {
method: 'POST',
body: JSON.stringify(changes),
}).then((r) => r.json()),
},
{ autoSync: true }
);
await syncManager.start();⚡ Benchmarks
TurboDB is engineered for extreme performance. Below are representative results comparing operations across popular storage solutions (measured on iPhone 15 Pro, 10,000 iterations).
| Operation | AsyncStorage | MMKV | TurboDB | | :-------------------------- | :----------: | :----: | :-------------: | | Write (Small Object) | ~850ms | ~45ms | ~12ms | | Read (Small Object) | ~420ms | ~35ms | ~8ms | | Batch Write (100 items) | ~1200ms | ~150ms | ~15ms (WAL) | | Range Query | ❌ | ❌ | ✅ <5ms |
[!NOTE] Methodology: Benchmarks performed on iPhone 15 Pro (iOS 17.4) and Pixel 8 (Android 14) using
react-native-performance. Each test represents an average of 10,000 iterations. See our benchmark suite for full details.
[!TIP] TurboDB is 10x faster than AsyncStorage and significantly outpaces MMKV in batch operations thanks to its Write-Ahead Log (WAL) architecture.
🆚 Why TurboDB? (Comparison)
| Feature | TurboDB | AsyncStorage | MMKV | SQLite (Bridge) | | :--------------- | :-----------------: | :----------: | :--------: | :-------------: | | Architecture | JSI C++ | Async Bridge | JSI C++ | Async Bridge | | Best For | Complex Objects | Simple Prefs | Primitives | Relational Data | | Encryption | ✅ XChaCha20 | ❌ | ❌ | ❌ | | Transactions | ✅ (WAL) | ❌ | ❌ | ✅ | | Indexing | ✅ B+Tree | ❌ | ❌ | ✅ | | Offline Sync | ✅ Built-in | ❌ | ❌ | ❌ |
🌐 Platform Support
| Platform | New Architecture | Old Architecture | Min Version | | :------------ | :--------------: | :--------------: | :------------ | | iOS | ✅ Full | ✅ Fallback | iOS 15.1+ | | Android | ✅ Full | ✅ Fallback | SDK 24+ (7.0) | | Web (SSR) | ✅ Full | ✅ Full | Chrome 90+ |
🚀 Examples
- Basic Example: Standard usage with React Hooks.
- Offline Sync Demo: Integration with SyncManager and remote backends.
🔧 Troubleshooting
"Failed to initialize storage" Error
[!CAUTION] Ensure you are using an absolute path.
- Use absolute path: Always use
TurboDB.getDocumentsDirectory()+ filename. - Add delay: If native modules aren't ready, add a 1-second delay before
TurboDB.create(). - Clean State: Call
await db.deleteAllAsync()if you need a fresh start after creation.
Native Module Not Found
- Run
cd ios && pod install. - Clean rebuild:
cd android && ./gradlew clean. - Verify
newArchEnabled=trueingradle.properties.
JSI Runtime Errors
[!WARNING] Do not use the constructor directly (
new TurboDB()).
Always use the async factory:
const db = await TurboDB.create(path, size);📄 License
MIT © Ganesh Jayaprakash
