qdrant_edge 0.8.0-dev.1
qdrant_edge: ^0.8.0-dev.1 copied to clipboard
On-device vector search for Dart and Flutter, powered by the qdrant-edge Rust engine through UniFFI — the same shared FFI crate as the Swift and Kotlin SDKs.
Qdrant Edge — Dart & Flutter #
On-device vector search for Dart and Flutter, powered by Qdrant Edge — the Qdrant search engine running in-process, no server and no network.
The SDK is a thin layer over the UniFFI binding generated from the shared
qdrant-edge-ffi Rust crate — the same crate the Swift and Kotlin SDKs bind,
so the three stay in lock-step by construction. Like the mobile SDKs, it builds
--no-default-features, so the O(n²) search_matrix op is off the surface.
Quick start #
import 'package:qdrant_edge/qdrant_edge.dart';
final shard = EdgeShard.load(
path: dir.path,
config: EdgeConfig(
vectorData: {'': VectorDataConfig(size: 4, distance: Distance.dot)},
),
);
shard.update(operation: UpdateOperation.upsertPoints(points: [
Point(id: NumIdPointId(1), vector: SingleVector([1, 0, 0, 0])),
]));
final hits = shard.search(request: SearchRequest(
query: NearestQuery(vector: DenseNamedVector([1, 0, 0, 0]), using: null),
limit: 10,
));
See example/ for a runnable demo and test/ for load → upsert → search,
persistence, and error-path coverage.
Public API #
lib/qdrant_edge.dart re-exports the generated binding through an explicit
show list, so the public surface is only the domain API (EdgeShard,
EdgeConfig, Point, the query/filter/vector/error types, …). The UniFFI
plumbing (FfiConverter*, RustBuffer, the call-status *ErrorHandlers, the
raw @Native C functions) lives in lib/src/ and is not exported — it stays out of the package's semver
contract, so a future UniFFI bump can reshape it without breaking consumers.
(Swift achieves the same by demoting plumbing to internal; Dart does it for
free with a show list, which Kotlin/JVM cannot.)
How it works #
- Bindings.
lib/src/qdrant_edge_ffi.dartis generated byuniffi-bindgen-dartin library mode (it reads the UniFFI metadata embedded in the compiled cdylib; the crate usessetup_scaffolding!(), so there is no UDL). It is not committed — it is ~17k lines of machine output,.gitignored and regenerated by./build.sh. Run./build.shonce after a fresh checkout beforedart test/dart analyze(the same way the Swift/Kotlin SDKs need their build script first).uniffi.tomlpins the Dart package name so the generated@Nativeasset ids resolve. Only the small curated facade (lib/qdrant_edge.dart) is committed — it is the reviewed public surface. - Toolchain provenance. The bindgen must be built from the fork that
carries the uniffi-0.32 + library-mode-CLI + enum-collision-fix work (upstream
acterglobal/uniffi-dartlacks it, pending PRs #149 / #151 / #152):github.com/DenisovAV/uniffi-dart@library-mode-cli. PointUNIFFI_BINDGEN_DARTat that build. CI regenerates the binding and diffs the committed facade to guarantee the public surface stays in sync with the crate (the binding itself can't drift — it is always regenerated, never stored). - Native library. The engine is delivered as a
Native Asset:
dart test,dart run, andflutter test/flutter runinvokehook/build.dartautomatically — no--enable-experimentflag on Dart 3.12+.
Distribution #
hook/build.dart provisions the engine three ways, in priority order, so the
same hook serves both an in-tree developer and a pub.dev consumer:
- Local prebuilt —
native/prebuilt/<os_arch>/(or$QDRANT_EDGE_PREBUILT_DIR). A dev/CI override, e.g. dropping in a locally cross-built device library. - From source (host, in-tree) — when the target is the host OS and the
Cargo workspace is present, build with
cargo +nightly(needs the nightly toolchain + protobuf). This is the fast path while developing inside the monorepo; incremental rebuilds re-run only when a source dir changes. - Download — otherwise fetch the per-platform, SHA256-pinned archive
from the GitHub Release (
edge-dart-native-v$VERSION) and cache it under the platform cache dir. This is how a consumer with no Rust toolchain, and every cross-compile (device/emulator) target, is served — the same shape the Kotlin AAR and Swift XCFramework releases use.
.github/workflows/edge-dart-native.yml builds the eight cdylibs (Linux
x86_64/arm64, Windows x86_64, macOS arm64, iOS arm64 device/simulator, Android
arm64/x86_64), packages each as qdrant-edge-ffi-<os_arch>.tar.gz, and publishes
them with a checksums.txt. Those SHA256 lines are pinned into the _sha256
table in hook/build.dart — the download path stays inert until they are, so a
tampered or truncated archive can never be linked in.
The native-prebuilt release (edge-dart-native-v0.8.0) is cut and its checksums
are pinned, so the package is publishable to pub.dev as qdrant_edge. Publishing
regenerates the binding first (make publish runs ./build.sh then
dart pub publish) so the generated glue lands in the archive even though it is
.gitignored — .pubignore replaces .gitignore for pub and does not exclude
it. native/ and the on-device harness stay out (a ~55 KB archive: the Dart glue
ships as source, the native engine is downloaded at build time, never bundled).
Versioning #
The version tracks lib/edge/VERSION (the published Qdrant Edge line) — the same
single source the Swift edge-v$VERSION tag and the Kotlin VERSION_NAME use.
edge-dart.yml enforces pubspec version == crate version == lib/edge/VERSION.