Inspect the embedded databases inside your running Dart or Flutter application, from a desktop client, over a paired connection.
Add one package to your app, forward a port, and browse your live data — no
adb pull, no copying a .db off the device and wondering whether you also
took its -wal.
These are the Apache-2.0 client-side libraries. They are engine-neutral and product-neutral: they speak a documented wire protocol, and any client that speaks it can drive them.
The protocol is not a Dart protocol. A bridge is whatever speaks it, and this repository holds the engine-neutral core for each language it is implemented in, beside the specification that binds all of them.
| Language | Core | Status | Registry |
|---|---|---|---|
| Dart / Flutter | dart/dbinspect_bridge |
shipped | pub.dev dbinspect_bridge |
| JVM — desktop and native Android | jvm/dbinspect-bridge |
shipped | Maven Central org.dizitart:dbinspect-bridge |
| Rust | rust/dbinspect-bridge |
M4, not yet written | crates.io dbinspect-bridge |
A core depends on no database. It is the protocol, pairing, the transport and the release guard. Everything that knows about an actual storage engine is a separate adapter that depends on a core:
| Adapter | Language | Lives in |
|---|---|---|
dart/dbinspect_sqflite — SqfliteAdapter |
Dart | here |
dbinspect_drift, dbinspect_hive (M8) |
Dart | here |
nitrite_bridge |
Dart | nitrite-flutter packages/ |
org.dizitart:nitrite-bridge |
JVM | nitrite-java nitrite-bridge/ |
nitrite-bridge |
Rust | nitrite-rust nitrite-bridge/ |
One package here is neither a core nor an adapter:
dart/dbinspect_mdns is optional mDNS advertising, so a
client can find a bridge on the LAN. It is separate for the same reason a core
depends on no database — bonsoir is a Flutter plugin, and the core must stay
usable from a server-side or CLI application. Discovery is opt-in and secondary
to adb forward anyway.
An adapter never depends on another adapter, so inspecting a SQLite database pulls in no Nitrite and inspecting a Nitrite database pulls in no SQLite — and, because the core is here rather than inside a database's own repository, you can inspect a JVM or Rust database that has nothing to do with Nitrite by writing an adapter against the core. That is the whole reason the split exists. A JDBC, H2, MapDB, redb or sled adapter is a downstream package; none is scheduled here, and none needs anything from us to be written.
This repository sits in the nitrite organisation, and that says nothing
about what it depends on. It is there because docs/THREAT-MODEL.md §7 is the
acceptance criteria for the three nitrite-bridge adapters above, and a
specification is easier to maintain beside the implementations it binds.
import 'package:dbinspect_bridge/dbinspect_bridge.dart';
import 'package:dbinspect_sqflite/dbinspect_sqflite.dart';
await startBridge(
appName: 'example_app',
adapters: [
SqfliteAdapter(executor: db, id: 'app', displayName: 'app.db'),
],
);The pairing code is printed to your logger in a banner. Type it into the client to connect.
It is not in your release build. startBridge returns null behind a
compile-time constant, so the AOT compiler drops the server and the protocol
strings entirely. Opt back in on purpose with
--dart-define=DBINSPECT_BRIDGE=true.
It binds 127.0.0.1. Reaching it from another machine is meant to be a
deliberate act — adb forward tcp:9000 tcp:9000 or an SSH tunnel. Setting
bindAddress forces TLS with a certificate generated for the session, whose
SHA-256 fingerprint goes in the banner for the client to pin.
Everything beyond reading is off. edit, sql and snapshot are false
and regex is absent unless you opt in per adapter. An operation that is off is
absent from the reported capabilities, not merely refused when called.
Pairing is not a formality. 40 bits, regenerated per run, compared in constant time, with a failure budget that is per bridge session rather than per connection — ten wrong guesses close pairing until the application restarts.
| Document | What it covers |
|---|---|
| docs/PROTOCOL.md | The JSON-RPC wire protocol, frozen at v1. Additive changes only. |
| docs/THREAT-MODEL.md | The threat model. §5 is binding architecture, §7 is binding acceptance criteria. |
Both are binding on every implementation of the protocol, in every language, and
conformance/ is how that is checked rather than asserted: a
black-box suite that pairs with a running bridge and reads what it puts on the
wire, taking a host:port and a pairing code and nothing else. The same
invocation drives the Dart bridge and the JVM bridge today, unmodified, and the
Rust one when it lands — which is what freezing the protocol was for.
cd conformance && dart run bin/dbinspect_conformance.dart 127.0.0.1:53219 J8K4M2QXThe core and the adapters are plain dart pub — pure Dart, no Flutter, no
workspace wiring. They must stay that way: they are embedded in other people's
applications and requiring a Flutter SDK to build one would be a real cost to a
server-side or CLI user.
for p in dart/dbinspect_bridge dart/dbinspect_sqflite; do (cd "$p" && dart pub get && dart format --output=none --set-exit-if-changed . && dart analyze --fatal-infos && dart test) || break; donedart/dbinspect_mdns is the exception, and being the exception is why it is a
package of its own:
cd dart/dbinspect_mdns && flutter pub get && flutter analyze --fatal-infos && flutter testThe JVM core is Maven, Java 11, and has a CI job of its own. rust/ gets one
when it lands. The language is a directory, not the shape of the repository.
cd jvm/dbinspect-bridge && mvn -B verifyIts reference bridge is in test sources — the published jar has no adapter and no database in it, and a test asserts that — so pointing the conformance suite at it is two commands:
cd jvm/dbinspect-bridge && ./tool/run_reference_bridge.sh # prints {"host":…,"port":…,"code":…}
cd conformance && dart run bin/dbinspect_conformance.dart <host:port> <code>Two checks need more than a Dart SDK and so have jobs of their own.
dart/dbinspect_sqflite/example/flutter_app
is a Flutter app with the bridge in it over a 50k-row table, and it is what they
run against:
cd dart/dbinspect_sqflite/example/flutter_app && ./tool/verify_release_apk.sh
dart tool/measure_page_latency.dart <host:port> <code>The first builds the release APK and greps it for the protocol strings — §7
criterion 2 on a shipped artifact rather than on a probe binary — with the
--dart-define opt-in as its negative control. The second measures what a
client waits for a page, reporting the round trip and the adapter's own share
separately, and runs against a bridge in any language.
conformance/ is a job of its own and is minutes long: one of the
criteria it carries is "ten wrong pairing codes close the gate", and the bridge's
backoff is a real four-minute wait that exists to be that slow.
Fanlight is a desktop client for this protocol
(Windows / macOS / Linux) and is a separate, commercial product. The protocol is
open and these libraries are Apache-2.0; nothing here depends on Fanlight, and
writing another client requires only docs/PROTOCOL.md.
Apache-2.0. See LICENSE.