npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fireflydb/op-sqlite-driver

v0.0.11

Published

Bare React Native (TurboModule) drivers for FireflyDB. Bundles libfirefly and wires it through @op-engineering/op-sqlite, a pluggable secure-storage driver, and React Native WebSocket. No expo-modules.

Downloads

1,203

Readme

@fireflydb/op-sqlite

Bare React Native (TurboModule) drivers for FireflyDB. Bundles the Rust libfirefly SQLite extension and wires it into @op-engineering/op-sqlite, a pluggable secure-storage driver, and the React Native global WebSocket. Exposes a change-listener bridge so JS gets notified of CRDT-relevant local writes.

This is the non-Expo counterpart to @fireflydb/expo: same Rust core and listener contract, but the native module is a codegen TurboModule (no expo-modules-core) and SQLite is op-sqlite instead of expo-sqlite.

New Architecture required. The change-listener uses the TurboModule event emitter, which only exists on the New Architecture (newArchEnabled=true, the default in React Native 0.76+).

This document focuses on how the native core is built and shipped. For SDK usage, see packages/core.


Install

npm install @fireflydb/op-sqlite @op-engineering/op-sqlite react-native-get-random-values
cd ios && pod install

@op-engineering/op-sqlite and react-native-get-random-values are peer dependencies. op-sqlite must be built with extension loading enabled — do not set iosSqlite: true in its config (the embedded iOS system SQLite forbids sqlite3_load_extension; op-sqlite's bundled SQLite allows it, which is the default).

Web

The package ships a web entry (src/index.web.ts, resolved automatically by Metro's platform extensions) targeting op-sqlite's web backend (≥15.2.11). Web cannot load extensions at runtime, so firefly is statically linked into the SQLite wasm instead: alias op-sqlite's optional @sqlite.org/sqlite-wasm peer to our drop-in superset, and install the browser drivers:

npm install @fireflydb/web   # browser WebSocket driver + IndexedDB storage
"dependencies": {
  "@sqlite.org/sqlite-wasm": "npm:@fireflydb/[email protected]"
}

App code is the same createFireflyClient({ sqliteOptions: { db }, … }) — pass the connection from openAsync({ name }). Live updates work the same as native: the wasm registers libfirefly's change listener inside its worker (firefly_web_subscribe()) and pushes each local write out over a BroadcastChannel the driver listens on — this needs the aliased wasm to be ≥353.2.8 (older builds lack the bridge; the client then logs a warning and live push is disabled until the next sync round). Other differences on web: the page must be cross-origin isolated (COOP/COEP headers — op-sqlite's web backend uses the SharedArrayBuffer OPFS VFS); the driver verifies at open() that the loaded wasm actually has firefly and fails with a pointer to the alias line if not. Non-Metro bundlers need to alias the package to the web entries themselves — the working Vite recipe and how @fireflydb/sqlite-wasm is produced are both in sdk/typescript/packages/web/sqlite-wasm/README.md.

Usage

The app opens the op-sqlite connection and hands it to the client. libfirefly's CRDT triggers and per-connection device_id live on that ONE connection, so the app's own reads/writes (drizzle, raw queries) must run on the SAME handle to be captured and synced — a second connection to the file would lack the extension, and the trigger-invoked firefly_* functions wouldn't exist there.

import { open } from '@op-engineering/op-sqlite';
import { createFireflyClient } from '@fireflydb/op-sqlite';
import { drizzle } from 'drizzle-orm/op-sqlite'; // optional — any consumer of the handle

const handle = open({ name: 'app.db' });  // the app owns this connection
const db = drizzle(handle, { schema });   // same connection -> app writes sync

const client = createFireflyClient({
  relayUrl,
  databaseID,
  dbName,
  sqliteOptions: { db: handle },    // the handle names the database file
  token,                            // your TokenProvider (OIDC flow)
  secureStorage: myKeychainDriver,  // bring your own; see below
});

await client.init(); // loads libfirefly onto the shared connection
await client.open();

// Live local writes (encoded RowState batches), e.g. to ship upstream.
const sub = client.subscribeToChanges((event) => {
  // event.source: 'local' | 'remote'; event.blob: Uint8Array
});
sub?.remove();

Ownership follows who opened: the driver enforces its setup invariants on the handle (WAL + foreign_keys pragmas, loading libfirefly — don't load it yourself), but client.close() detaches without closing it, so the app's database keeps working across sign-out or a client swap. Closing the handle is the app's job.

dbPath is optional with this driver — the handle already names the file. Supplying one turns it into a cross-check: the client throws at init() if it doesn't match the file the handle is open on.

Secure storage is pluggable

Unlike the Expo package (which bundles expo-secure-store), this package does not pull a secure-storage native dependency — you supply any SecureStorageDriver (get/set/delete of Uint8Array). Common choices: react-native-keychain, an encrypted react-native-mmkv instance, or even expo-secure-store. For development and tests only, InMemorySecureStorage is exported (non-persistent, no at-rest protection — never use it for a real device key).


Layout

packages/op-sqlite/
├── cpp/                          # Cross-platform C++ shared by iOS + Android
│   ├── firefly_abi.h             # Rust C-ABI declarations (firefly_add_change_listener, …)
│   ├── firefly_listener.h        # Listener registry public surface
│   └── firefly_listener.cpp      # Process-wide handle→dispatch registry
│
├── src/
│   ├── NativeFireflyClient.ts    # TurboModule codegen spec (the cross-platform contract)
│   ├── FireflyClientModule.ts    # JS wrapper: subscribeToChanges + base64 decode
│   ├── index.ts                  # createFireflyClient + re-exports
│   └── drivers/                  # op-sqlite / websocket / secureStorage
│
├── FireflyClient.podspec         # at the package ROOT — RN CLI autolinking only globs *.podspec there
│
├── ios/
│   ├── Firefly.xcframework/      # ← produced by scripts/build-libfirefly.sh
│   ├── FireflyClient.mm          # ObjC++ TurboModule implementing NativeFireflyClientSpec
│   └── _shared/                  # file symlinks into ../cpp (CocoaPods quirk; see ios/.gitignore)
│
├── android/
│   ├── build.gradle              # com.facebook.react codegen + CMake + jniLibs
│   └── src/main/
│       ├── cpp/CMakeLists.txt    # Imports prebuilt libfirefly.so, builds libfirefly_jni.so
│       ├── cpp/firefly_jni.cpp   # JNI shim
│       ├── jniLibs/<abi>/libfirefly.so   # ← produced by scripts/build-libfirefly.sh
│       └── java/com/fireflydb/opsqlite/  # FireflyClientModule / Package / JniBridge
│
└── scripts/
    └── build-libfirefly.sh       # Cross-compiles core/ for iOS + Android

The single source of truth for the native core is the core/ Rust crate at the repo root. Everything under ios/Firefly.xcframework/ and android/src/main/jniLibs/ is build output (gitignored) — never hand-edit it.


How the change-listener works

op-sqlite connection ──loadExtension(getLibraryPath())──▶ libfirefly (Firefly.framework / libfirefly.so)
                                                                  │  local CRDT write
                                                                  ▼
firefly_add_change_listener  ──fires on SQLite writer thread──▶  cpp/firefly_listener.cpp (shared registry)
                                                                  │  dispatch(handle, blob)
                          ┌───────────────────────────────────────┴───────────────────────────────┐
                          ▼ iOS                                                                      ▼ Android
        FireflyClient.mm fireflyDispatch                                       firefly_jni.cpp → FireflyJniBridge.dispatch
        [module emitOnFireflyChange:@{id, blobBase64}]                        emitOnFireflyChange({id, blobBase64})
                          └───────────────────────────────────────┬───────────────────────────────┘
                                                                   ▼
                              onFireflyChange event (marshalled to the JS thread by the codegen emitter)
                                                                   ▼
                              FireflyClientModule.subscribeToChanges → base64-decode → handler(Uint8Array)

Both platforms link the same Firefly binary that op-sqlite dlopens as an extension, so the C-ABI listener observes writes made through op-sqlite's connection. The blob travels as base64 because codegen event payloads can't carry binary; per-write RowState batches are small.

The listener registry (cpp/firefly_listener.cpp) is byte-identical to the Expo package's — it implements libfirefly's lifetime contract (a fire may briefly outlive firefly_remove_change_listener) once, via opaque integer handles.


Rebuilding the core

pnpm build:libfirefly         # both platforms
bash scripts/build-libfirefly.sh ios       # iOS only
bash scripts/build-libfirefly.sh android   # Android only

The script cross-compiles core/ with --no-default-features --features loadable_extension so the resulting binary is a SQLite loadable extension (no bundled SQLite — op-sqlite ships its own). The .xcframework and .so files are gitignored build output: the prepack hook rebuilds them on pnpm pack / pnpm publish, so the published package ships binaries and consumers never need a Rust toolchain. In a fresh clone, run pnpm build:libfirefly once before building the example app. See the Expo package README for the full prerequisites table and the iOS/Android pipeline diagrams — the build is shared.

The shared C++ in cpp/ is not built by this script — it's compiled by the platform build systems (CocoaPods on iOS, Gradle/CMake on Android) when the host app builds.