Architecture
An overview of PomegranateDB's internal architecture.
Layer Diagram
┌──────────────────────────────────────────────┐
│ React Hooks │
│ useLiveQuery · useById · useSearch · useCount│
├──────────────────────────────────────────────┤
│ Database │
│ Writer queue · Model registry · Events │
├──────────────────────────────────────────────┤
│ Collection │
│ create · findById · query · search · observe │
├──────────────────────────────────────────────┤
│ QueryBuilder │
│ where · orderBy · limit · and/or/not │
├──────────────────────────────────────────────┤
│ StorageAdapter (17 methods) │
│ initialize · find · count · batch · sync... │
├─────────────────────┬────────────────────────┤
│ LokiAdapter │ SQLiteAdapter │
│ (in-memory) │ (SQL generation) │
│ │ ┌──────────────┐ │
│ │ │ SQLiteDriver │ │
│ │ └──────┬───────┘ │
│ │ Expo ─ op-sqlite ─ JSI│
├─────────────────────┴────────────────────────┤
│ Observable System │
│ Subject · BehaviorSubject · SharedObservable │
├──────────────────────────────────────────────┤
│ Schema / Model │
│ m.model · m.text · m.belongsTo · Model<S> │
├──────────────────────────────────────────────┤
│ Sync Engine │
│ performSync · push-first · conflict resolve │
└──────────────────────────────────────────────┘
Key Design Decisions
Schema-First, No Decorators
Unlike WatermelonDB which uses decorators (@field, @text, @relation), PomegranateDB uses a builder pattern:
// PomegranateDB — schema-first
const PostSchema = m.model('posts', {
title: m.text(),
author: m.belongsTo(() => UserSchema, { key: 'author_id' }),
});
class Post extends Model<typeof PostSchema> {
static schema = PostSchema;
get author() { return this.belongsTo('author'); }
}
This means:
- No Babel decorator transform needed
- Types flow naturally from schema to model
- The schema is a plain frozen object — serializable and inspectable
Injectable SQLite Drivers
The SQLiteAdapter doesn't depend on any specific SQLite library. Instead, it takes a driver that implements 5 methods:
interface SQLiteDriver {
open(name: string): Promise<void>;
execute(sql: string, bindings?: unknown[]): Promise<void>;
query(sql: string, bindings?: unknown[]): Promise<Record<string, unknown>[]>;
executeInTransaction(fn: () => Promise<void>): Promise<void>;
close(): Promise<void>;
}
This allows wrapping any SQLite library (expo-sqlite, op-sqlite, better-sqlite3, sql.js, etc.) with a thin adapter.
Synchronous JSI Bridge
The Native JSI adapter (native/shared/Database.cpp) installs a global function via JSI:
global.nativePomegranateCreateAdapter(dbName) → { execute, query, executeBatch, close }
All methods on the returned object are synchronous C++ calls. The TypeScript driver wraps them in Promises for the SQLiteDriver interface, but there is zero async overhead at the native level.
Observable-Based Reactivity
PomegranateDB uses its own lightweight observable system rather than RxJS:
Subject<T>— emit values to multiple subscribersBehaviorSubject<T>— remembers and replays the latest valueSharedObservable<T>— shared subscription with automatic cleanup
This keeps the bundle small and avoids the RxJS dependency.
Writer Queue
All mutations go through db.write(), which serializes them through a single queue:
await db.write(async () => {
// Only one writer can be active at a time
});
This prevents data races and ensures database consistency without manual locking.
C++ Architecture (Native JSI)
native/shared/
Sqlite.h / .cpp — RAII SQLite wrapper (SqliteDb, SqliteStatement)
Database.h / .cpp — JSI bridge (statement cache, arg binding, row conversion)
native/android-jsi/
src/main/java/ — Java: PomegranateJSIModule, JSIInstaller, PomegranateJSIPackage
src/main/cpp/ — JNI bridge, Android platform (path resolution, logging)
CMakeLists.txt (builds libpomegranate-jsi.so)
The C++ code:
- Opens SQLite with optimal pragmas (WAL, NORMAL sync, 8MB cache)
- Caches prepared statements for repeated queries
- Binds JSI values to SQLite parameters (null, bool, int64, double, text)
- Converts SQLite result rows to JSI objects
- Runs batch operations in a single transaction with ROLLBACK on failure