Build powerful React and React Native apps that scale from hundreds to tens of thousands of records and remain fast ⚡️
Lazy-loaded — only fetch data when you need it. Your app starts fast no matter how much data you have.
Handle hundreds to tens of thousands of records with consistent performance.
Built-in sync protocol with push/pull reconciliation. Works offline, syncs when connected.
Reactive hooks keep your UI in sync automatically. Zero boilerplate subscriptions.
LokiJS, Expo SQLite, op-sqlite, or JSI C++. Swap storage backends without changing app code.
Optional AES-GCM encryption or SQLCipher via op-sqlite. Protect user data on-device.
- Schema-first: Define your models declaratively with full type inference
- Reactive queries: UI updates automatically when data changes — via hooks or observables
- Offline-first sync: Built-in pull/push protocol that works offline and reconciles on reconnect
- Pluggable adapters: Choose the right storage backend for your app — LokiJS (in-memory), Expo SQLite, op-sqlite, or our own JSI C++ adapter
- TypeScript-native: The whole codebase is idiomatic TypeScript. Types flow from schema to model to query to component
import { m, Model, Database, LokiAdapter } from 'pomegranate-db';
// 1. Define your schema
const PostSchema = m.model('posts', {
title: m.text(),
body: m.text(),
status: m.text(),
createdAt: m.date('created_at').readonly(),
});
// 2. Create a model class
class Post extends Model<typeof PostSchema> {
static schema = PostSchema;
}
// 3. Initialize the database
const db = new Database({
adapter: new LokiAdapter({ databaseName: 'myapp' }),
models: [Post],
});
await db.initialize();
// 4. Write data
await db.write(async () => {
await db.get(Post).create({
title: 'Hello World',
body: 'My first post',
status: 'draft',
});
});
// 5. Query data
const posts = await db.get(Post)
.query()
.where('status', 'draft')
.fetch();import { DatabaseProvider, useLiveQuery } from 'pomegranate-db';
function App() {
return (
<DatabaseProvider value={db}>
<PostList />
</DatabaseProvider>
);
}
function PostList() {
const { results: posts, isLoading } = useLiveQuery(Post, (q) =>
q.where('status', 'eq', 'published').orderBy('created_at', 'desc'),
);
if (isLoading) return <Text>Loading...</Text>;
return posts.map((post) => (
<Text key={post.id}>{post.title}</Text>
));
}npm install pomegranate-dbInstall adapter-specific peers only when you use those entry points:
pomegranate-db->reactpomegranate-db/expo->react,expo-sqlitepomegranate-db/encryption-> no extra peers, uses Web Cryptopomegranate-db/encryption/node-> Node.js onlypomegranate-db/encryption/react-native-> React Native / Expo Web Crypto runtimepomegranate-db/op-sqlite->@op-engineering/op-sqlitepomegranate-db/native-sqlite-> React Native app with the bundled native module
PomegranateDB ships a small set of explicit subpath exports for common setups:
import { Database, LokiAdapter } from 'pomegranate-db'
import { createExpoSQLiteDriver } from 'pomegranate-db/expo'
import { EncryptingAdapter } from 'pomegranate-db/encryption'
import { nodeCryptoProvider } from 'pomegranate-db/encryption/node'
import { createOpSQLiteDriver } from 'pomegranate-db/op-sqlite'
import { createNativeSQLiteDriver } from 'pomegranate-db/native-sqlite'The root package intentionally excludes encryption exports so Expo Snack can install
pomegranate-db without resolving Node's crypto module. Import encryption through
the explicit ./encryption, ./encryption/node, or ./encryption/react-native
subpaths instead.
Manual migrations are supported across Loki and SQLite adapters with these step types:
createTableaddColumndestroyTablesql
Use sql for targeted backfills such as setting a new column value on existing rows.
Schema diff generation is not automated yet, so migration steps are still authored manually.
- Installation — add PomegranateDB to your project
- Schema & Models — define your data model
- CRUD Operations — create, read, update, delete
- React Hooks — reactive UI integration
MIT
