weaveffi-whisper-everywhere is the next entry in WeaveFoundry's *-everywhere
demo series, built to prove — in the most concrete, shareable way possible —
that a single WeaveFFI interface definition can deliver one native library to
every cross-platform framework at once. The goal: take whisper.cpp (Georgi
Gerganov's MIT-licensed C/C++ engine that runs OpenAI's Whisper speech-to-text
models) and expose a focused slice of its C API through one WeaveFFI IDL, then
run identical on-device transcription side-by-side in three apps — React
Native, NativeScript, and Flutter — all calling the exact same native core. The
architecture: a thin Rust shim links whisper.cpp and implements WeaveFFI's
stable C ABI, compiling to one cdylib plus a generated N-API addon; Flutter
consumes the C ABI via dart:ffi, while NativeScript (via its new N-API
runtime) and React Native (via react-native-node-api) load the same generated
N-API addon — the same module that also runs under Node. The thesis worth
proving is that React Native's New Architecture (JSI/C++), NativeScript's N-API
runtime, and Flutter's dart:ffi are all independently converging on the same
C/N-API native boundary that WeaveFFI already targets — so "define once, run
everywhere" stops being aspirational, and the same IDL additionally emits Swift,
Python, Go, and .NET packages for free.
idl/whisper.yml ← the single source of truth
│
weaveffi generate (v0.7.0)
│
┌──────────┬──────────┼──────────┬──────────┬───────┐
▼ ▼ ▼ ▼ ▼ ▼
generated/ generated/ generated/ generated/ …swift …go
c/ dart/ node/ python/ dotnet
rust/crates/whisper-core ──implements──▶ the WeaveFFI C ABI
│ links whisper.cpp (via whisper-rs)
▼
libweaveffi.{dylib,so,dll} + weaveffi N-API addon (index.node)
│ │
│ dart:ffi │ require('…/index.node')
▼ ▼
┌─────────┐ ┌───────────────────────────────────┐
│ Flutter │ │ @weaveffi-whisper/core (one JS) │
└─────────┘ │ ├── Node CLI │
│ ├── React Native (node-api) │
│ └── NativeScript (N-API runtime) │
└───────────────────────────────────┘
One IDL → one Rust core → four runtimes reaching the identical
weaveffi_whisper_* symbols. The three JS runtimes don't just share an ABI —
they share the same JavaScript (packages/whisper-weave-node).
Everything below was run end-to-end on this machine and produces the same
transcript of assets/jfk.wav ("And so my fellow Americans…"):
| Runtime | Path to the core | Status |
|---|---|---|
| Rust | direct C ABI call (cargo run --example) |
✅ verified |
| Node.js | WeaveFFI N-API addon | ✅ verified |
| Dart / Flutter | WeaveFFI dart:ffi binding |
✅ verified (headless FFI run; macOS desktop ready) |
| React Native | @weaveffi-whisper/core via react-native-node-api |
🟡 wired — device build needs the core cross-compiled (see app README) |
| NativeScript | @weaveffi-whisper/core via the N-API runtime |
🟡 wired — device build needs the core cross-compiled (see app README) |
The RN and NativeScript apps run the exact same @weaveffi-whisper/core
JavaScript that the verified Node path exercises; what remains for a physical
device is building the Rust core for the device architecture and bundling the
.node addon (documented per app).
idl/whisper.yml The interface. Edit this, regenerate everything.
rust/crates/whisper-core/ Rust shim: links whisper.cpp, implements the C ABI.
Builds as `libweaveffi` (cdylib) + an rlib.
generated/ WeaveFFI output: c, dart, node, swift, python, go, dotnet.
packages/whisper-weave-node/ Shared JS package (@weaveffi-whisper/core) wrapping
the N-API addon — used by Node, RN, and NativeScript.
apps/flutter/ Flutter app (dart:ffi → generated/dart).
apps/react-native/ Expo app (react-native-node-api → the addon).
apps/nativescript/ NativeScript app (N-API runtime → the addon).
tools/dart-smoke/ Minimal headless Dart FFI proof.
scripts/ fetch-model, build-core, gen-bindings, build-node-addon.
Makefile Orchestration (`make help`).
Prerequisites: Rust (stable), Node 18+, and for the apps, Flutter / the Expo CLI / the NativeScript CLI as needed. CMake + a C/C++ toolchain are required to build whisper.cpp the first time.
# 1. Fetch a small model + the sample clip, build the core, regenerate all
# bindings, and build the N-API addon.
make setup # MODEL=tiny.en by default
# 2. Prove the pipeline three ways (all print the same transcript):
make rust-smoke # Rust → C ABI
make node-smoke # Node → N-API
make dart-smoke # Dart → dart:ffi
# 3. Launch an app:
make run-flutter # macOS desktop, loads the host dylib
make run-rn # Expo dev server
make run-ns # NativeScriptmake help lists every target.
- Flutter imports
package:weaveffi(verbatimgenerated/dart) and calls theweaveffi_whisper_*C symbols overdart:ffi. Seeapps/flutter/README.md. - React Native imports
@weaveffi-whisper/core;react-native-node-api's Babel plugin rewrites therequire('*.node')so the addon is bundled and the N-API host resolves it on device. Seeapps/react-native/README.md. - NativeScript imports the same
@weaveffi-whisper/core; its built-in N-API runtime loads the addon. Seeapps/nativescript/README.md.
Because the three JS runtimes share packages/whisper-weave-node, the app code
is identical: Model.load(path) → model.transcribeFile(wav, 'en') →
transcript.toJSON().
Edit idl/whisper.yml, then:
make bindings # regenerate all language bindings
make addon # rebuild the N-API addon C glue + restageImplement any new functions in rust/crates/whisper-core/src/lib.rs (the
generator can scaffold matching stubs with weaveffi generate … --target rust).
If you scaffold NativeScript yourself and have min-release-age set in your
~/.npmrc, ns create can crash with this.before.toISOString is not a function — NativeScript's bundled pacote passes npm's computed before
string where a Date is expected. Bypass it for just that one command
without touching your global config:
npm_config_userconfig=/dev/null ns create nativescript \
--template @nativescript/template-blank-ts --appid com.weavefoundry.whispernsDemo code: MIT. whisper.cpp and the ggml models are MIT-licensed by their
respective authors.