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/expo-driver

v0.0.11

Published

Expo native module + drivers for FireflyDB. Bundles libfirefly and wires it through expo-sqlite, expo-secure-store, and React Native WebSocket.

Readme

@fireflydb/expo

Expo native module that bundles the Rust libfirefly SQLite extension and wires it into expo-sqlite, expo-secure-store, and React Native WebSocket. Exposes a change-listener bridge so JS gets notified of CRDT-relevant local writes.

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


Layout

packages/expo/
├── 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
│
├── ios/
│   ├── Firefly.xcframework/      # ← produced by scripts/build-libfirefly.sh
│   ├── FireflyClient.podspec     # Vends the xcframework + symlinks ../cpp into the pod
│   └── FireflyClientModule.swift
│
├── android/
│   ├── build.gradle              # Wires jniLibs + externalNativeBuild (CMake)
│   └── 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
│
├── scripts/
│   └── build-libfirefly.sh       # Cross-compiles core/ for iOS + Android
│
└── src/                          # TypeScript surface (drivers + module bindings)

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.


Rebuilding the core

Run from this package directory:

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

The script (scripts/build-libfirefly.sh) cross-compiles core/ with --no-default-features --features loadable_extension so the resulting binary is a SQLite loadable extension (no bundled SQLite — both Expo and Android ship their own).

Prerequisites

| Tool | Used for | Auto-installed by script? | |------|----------|---------------------------| | rustup + Rust toolchain | All targets | No (must be on PATH) | | iOS rust targets (aarch64-apple-ios, aarch64-apple-ios-sim, x86_64-apple-ios) | iOS | Yes (rustup target add) | | Android rust targets (aarch64-linux-android, armv7-linux-androideabi, x86_64-linux-android, i686-linux-android) | Android | Yes | | xcodebuild, lipo, install_name_tool | iOS framework assembly | No (Xcode CLT) | | cargo-ndk | Android cross-compile | Yes (cargo install) | | Android NDK r24+ (ANDROID_NDK_HOME exported) | Android | No |

When to rebuild

Rebuild after any change under core/ that affects the C-ABI surface or runtime behavior. 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.

The shared C++ in cpp/ (firefly_listener.{cpp,h}) 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. Editing it requires no rebuild script run; just rebuild the host app.


iOS pipeline

core/  ──cargo build──▶  target/<rust-target>/release/libfirefly.dylib
                          │
                          ├─ aarch64-apple-ios          ─┐
                          ├─ aarch64-apple-ios-sim      ─┼─lipo──▶ sim-universal
                          └─ x86_64-apple-ios           ─┘
                                                          │
                          device-arm64.dylib              sim-universal.dylib
                                  │                              │
                          ios_make_framework            ios_make_framework
                                  ▼                              ▼
                  Firefly.framework (device)     Firefly.framework (sim-universal)
                                  └──── xcodebuild -create-xcframework ────┘
                                                  ▼
                                    ios/Firefly.xcframework/

What the script does, in order:

  1. Per-slice cargo build for each iOS target into the workspace target/ dir (the workspace lives at the repo root, not under core/).
  2. lipo the two simulator slices (arm64-sim + x86_64) into a single fat dylib. iOS device stays as a single arm64 slice.
  3. ios_make_framework wraps each dylib in a proper Firefly.framework bundle: copies the dylib in as the bundle binary (named Firefly, no extension), rewrites its LC_ID_DYLIB install name to @rpath/Firefly.framework/Firefly via install_name_tool, and writes an Info.plist with the right CFBundleSupportedPlatforms / MinimumOSVersion.
  4. xcodebuild -create-xcframework combines the device and simulator-universal frameworks into Firefly.xcframework. This is the canonical Apple distribution shape and the only one CocoaPods accepts via s.vendored_frameworks for dynamic linking.
  5. nm verification that _sqlite3_firefly_init is exported in both slices — that's the entry point expo-sqlite calls via sqlite3_load_extension.

FireflyClient.podspec then:

  • Vendors Firefly.xcframework (Xcode embeds the right slice into <App>.app/Frameworks/Firefly.framework/Firefly and re-signs at archive time).
  • Pulls in the shared C++ via ios/_shared/*.{cpp,h} (a committed directory of per-file symlinks pointing at ../cpp/*). This avoids a separate .mm shim — Swift binds to the C-ABI symbols via @_silgen_name. See the long comment in FireflyClient.podspec for why _shared/ is committed instead of generated by prepare_command (CocoaPods skips prepare_command for :path => pods).

Android pipeline

core/  ──cargo ndk build──▶  target/<rust-target>/release/libfirefly.so
                              │
                              ├─ aarch64-linux-android      ─▶ jniLibs/arm64-v8a/libfirefly.so
                              ├─ armv7-linux-androideabi    ─▶ jniLibs/armeabi-v7a/libfirefly.so
                              ├─ x86_64-linux-android       ─▶ jniLibs/x86_64/libfirefly.so
                              └─ i686-linux-android         ─▶ jniLibs/x86/libfirefly.so

(later, at app build time:)

android/src/main/cpp/firefly_jni.cpp + cpp/firefly_listener.cpp
                              │
                          CMake (externalNativeBuild)
                              ▼
                  jniLibs/<abi>/libfirefly_jni.so   (per ABI, links against libfirefly.so)

What the script does:

  1. cargo ndk --target <rust-target> --platform 24 for each ABI. The --platform 24 matches minSdkVersion 24 in android/build.gradle. cargo-ndk handles wiring the NDK toolchain into Cargo so the Rust compiler emits ABI-correct shared objects.
  2. Copies each libfirefly.so into android/src/main/jniLibs/<abi>/.

At app build time, Gradle (android/build.gradle):

  • Picks up the prebuilt libfirefly.so from jniLibs/<abi>/ and packages them into the APK/AAB — applicationInfo.nativeLibraryDir resolves them at runtime.
  • Compiles libfirefly_jni.so per ABI via externalNativeBuild / CMakeLists (android/src/main/cpp/CMakeLists.txt). CMake imports libfirefly.so as an IMPORTED target and links the JNI shim against it plus the shared cpp/firefly_listener.cpp.
  • packagingOptions { doNotStrip "**/libfirefly.so" } — the Rust release profile already strips, and stripping again can drop the sqlite3_firefly_init export.

Kotlin loads the native library with System.loadLibrary("firefly_jni"), which transitively pulls in libfirefly.so because of the import relationship in CMake.


Shared C++ (cpp/)

Both platforms include cpp/firefly_listener.cpp directly in their build (firefly_listener.cpp). It's a single process-wide registry mapping a caller-supplied int64_t handle to a platform dispatcher (Swift sendEvent on iOS, JNI CallStaticVoidMethod on Android). Keeping this in one place means the lifetime/race semantics required by firefly_add_change_listener's contract are implemented once, not twice.

Linking model:

  • iOS: compiled into the FireflyClient pod alongside the Swift module, with -fvisibility=hidden. Swift uses @_silgen_name to bind to firefly_listener_register / firefly_listener_unregister.
  • Android: compiled into libfirefly_jni.so by CMake. JNI calls into it directly.

The C-ABI between this layer and Rust (firefly_add_change_listener, firefly_remove_change_listener) is declared in cpp/firefly_abi.h.


Troubleshooting

sqlite3_firefly_init not exported. The script verifies this on iOS and aborts the build. If it ever fires, check that core/src/lib.rs still declares the entry point with the sqlite_extension_init macro and that release-profile strip = true isn't dropping the symbol on a new toolchain.

Android NDK not found. Export ANDROID_NDK_HOME (e.g. export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/27.1.12297006). cargo-ndk reads it.

linker 'cc' not found on Android targets. cargo-ndk injects the right linker via env vars only inside its child cargo invocation. If you ran cargo build --target aarch64-linux-android directly (not via cargo ndk), you'll get this. Use the script.

Stale target/ cache after toolchain bump. cargo clean at the repo root, then re-run the script. The targets are cross builds so they don't share intermediate artifacts with desktop dev builds, but a corrupt cache across a Rust version bump can manifest as missing symbols.

Undefined symbol: _firefly_listener_register at iOS link time. The shared C++ in cpp/ is pulled into the pod via the committed ios/_shared/ directory of per-file symlinks. Two CocoaPods quirks combined to cause earlier breakage here:

  1. CocoaPods indexes pod sources via Ruby's Find.find, which does NOT recurse into symlinked directories — so _shared cannot itself be a symlink. It must be a real directory containing file-level symlinks (which Find DOES list).
  2. CocoaPods does NOT run prepare_command for development (:path =>) pods. Anything that expects to be set up at pod install time silently no-ops.

So _shared/ is checked into git; if you add a file under cpp/, mirror it with ln -sfn ../../cpp/<name> ios/_shared/<name> and commit. After podspec or _shared/ changes, re-run pod install (or npx expo prebuild --clean for Expo apps) so the Pods xcodeproj regenerates with the new sources. The cannot link directly with 'SwiftUICore' line that often appears in the Expo CLI output is unrelated noise from Xcode 26 — check apps/<app>/.expo/xcodebuild.log for the real Undefined symbols section.