Skip to content
You are viewing the corvid 0.3.2 release snapshot — frozen at the 0.3.2 engine release.Current documentation

corvid-dart

corvid-dart is the Dart binding: it links the engine’s published FFI artifacts (the platform cdylib and corvid.h) through dart:ffi, with the raw layer generated by ffigen from the release’s own header (committed and drift-gated in CI), and carries an idiomatic Dart API on top. Deliberately the corvid-c/corvid-go pattern (a fetched, checksummed shared library), not the node/python one (Rust-source builds): ./fetch.sh (macOS/Linux) or ./fetch.ps1 (Windows) downloads and sha256-verifies the pinned release archive, dart test does the rest — no Rust toolchain, no vendored binaries. Dart VM on desktop (macOS, Linux, Windows x64; macOS + Linux arm64), CI-verified on all three operating systems; Flutter plugin packaging is a documented follow-up with a trigger (see the repo’s docs/PLAN.md platform story).

When to choose this binding: your project is Dart — a VM app, a CLI, a server — and you want corvid embedded as a plain pub dependency: documents are Map<String, Object?>, vectors are Float32List, keys are Uint8List, and every failure is a CorvidException.

C ABIcorvid-dart
opaque handles (corvid_db*, …)Db / Collection / Query classes with explicit idempotent close() and a NativeFinalizer backstop — close deliberately, the finalizer only keeps a leak from pinning engine memory
CORVID_ERR + thread-local last errorCorvidException carrying the ABI code + message, read off the thread-local slot immediately after the failing call
frozen enumsMetric, Quant, FieldType, … re-declared idiomatic Dart enums (exact ABI values)
consumed-by-call args (pred trees, builders)Predicate/Query consumed by their terminal (filter/and/run/aggregates); the finalizer is detached on consumption, close() frees an abandoned builder
borrowed views (_ref buffers, row docs, callback keys)copies at the boundary — every key/doc/vector is copied into Dart-owned memory inside the wrapper call before the next next() or free; nothing borrowed is retained
corvid_update_fn / corvid_scan_fnordinary Dart closures over NativeCallable.isolateLocal trampolines; a thrown exception is captured (never unwinding through native frames), the scan/update is aborted at the ABI level, and the original error + stack trace is rethrown at the call site
strings / bytes / vectorsString (UTF-8 at the boundary), Uint8List, Float32List — NaN/±inf/-0.0 cross bit-exact
documentsMap<String, Object?> / List<Object?> / null / bool / int (int64) / double

The raw ffigen layer stays importable as package:corvid_dart/src/... (internal); the public package:corvid_dart/corvid.dart leaks no dart:ffi type in any signature (the ABI’s ruling 3).

From the pinned release artifacts:

Terminal window
./fetch.sh # fetch + sha256-verify corvid v0.3.1 into deps/current
dart pub get
dart test # the golden suite (267 lines) + supplemental contracts
dart run examples/quickstart.dart

Requirements: the Dart SDK ^3.10 floor; CI runs the stable channel and 3.10.0 across linux/macos/windows (the toolchain policy — modern minimums, latest + previous in CI). Until the pub.dev publish is announced, consume from git (publish_to: none today):

dependencies:
corvid_dart:
git: https://github.com/corvid-db/corvid-dart.git

Engine v0.3.0’s ABI additions are first-class here:

  • Complete map decode — decoding a document enumerates every map’s keys through corvid_value_map_keys (the §4.4 string cursor), so whatever wrote the data, the Dart side sees the whole document. The mapkeys_test.dart contract pins the across-a-reopen shape.
  • Collection.phraseSearch(field, phrase, k) — the DIRECT positional search: consecutive, in-order analyzed tokens, stop words collapsing out of adjacency ("leaps over a sleeping" matching "jumps over a sleeping dog"-style text), rows carrying the BM25 phrase score (the phrase scale, not the builder’s fused RRF scale); k == 0 answers empty — inert, never an error. The text_search example demonstrates all of it, CJK bigram phrases included.

Six runnable programs under the repo’s examples/ directory (dart run examples/<name>.dart), executed on every CI leg with deterministic output: quickstart, hybrid (the flagship below), vector_index (in-memory / on-disk / binary-quantized HNSW vs the exact scan, plus a close/reopen), text_search (BM25 incl. CJK bigram segmentation, plus the v0.3.0 direct phraseSearch), graph (neighbors/traverse + delete cascade), and geo (radius / bbox / nearest with haversine kilometres). The quickstart and hybrid sources are embedded below — imported from the repo so they cannot drift from what CI executes (scripts/sync-binding-examples.sh; the drift gate reddens docs CI if they diverge).

void main() {
final db = Db.openMemory();
final docs = db.collection('docs');
docs.insert(kb('p1'), {
'title': 'rust embedded database',
'kind': 'doc',
'v': Float32List.fromList([1.0, 0.0]),
});
docs.insert(kb('p2'), {
'title': 'python web frameworks',
'kind': 'doc',
'v': Float32List.fromList([0.0, 1.0]),
});
docs.insert(kb('p3'), {
'title': 'rust again database',
'kind': 'doc',
'v': Float32List.fromList([0.9, 0.1]),
});
// kNN: the 3 nearest documents to (1, 0) under cosine. Row.doc is
// materialized only under select — retrieval rows carry keys and
// scores, so select the field the printout needs.
final rows = docs
.query()
.vector('v', Float32List.fromList([1.0, 0.0]), 3, Metric.cosine)
.select(['title'])
.run();
var rank = 0;
for (final r in rows) {
print('${++rank}. ${String.fromCharCodes(r.key)} '
'score=${r.score.toStringAsFixed(6)} ${r.doc}');
}
docs.close();
db.close();
}
void main() {
final db = Db.openMemory();
final docs = db.collection('docs');
docs.insert(kb('s1'), {
'kind': 'doc',
'body': 'rust embedded database',
'v': Float32List.fromList([1.0, 0.0]),
});
docs.insert(kb('s2'), {
'kind': 'doc',
'body': 'python web frameworks',
'v': Float32List.fromList([0.0, 1.0]),
});
docs.insert(kb('s3'), {
'kind': 'doc',
'body': 'rust again database',
'v': Float32List.fromList([0.9, 0.1]),
});
docs.insert(kb('m1'), {'kind': 'meta'}); // filtered out below
// The flagship query: filter + vector + text, RRF + MMR + limit.
final rows = docs
.query()
.filter(field('kind').eq('doc'))
.vector('v', Float32List.fromList([1.0, 0.0]), 2, Metric.cosine)
.text('body', 'rust database', 2)
.fuseRRF(60)
.rerankMMR(1.0)
.limit(2)
.select(['body'])
.run();
var rank = 0;
for (final r in rows) {
print('${++rank}. ${String.fromCharCodes(r.key)} '
'score=${r.score.toStringAsFixed(6)} ${r.doc}');
}
docs.close();
db.close();
}

dart test replays the engine’s entire golden fixture suite — 267 executable lines across 8 files, including the v0.3.0 VMAP_KEYS (map-key iteration) and PHRASE/PHRASE_K0 (direct positional search) lines — against the downloaded cdylib, through the binding (test/golden_test.dart): every counted line must dispatch, the first failure names file:line + OP + expected-vs-got, and one SMOKE <file> lines=<n> executed=<n> line per fixture proves the dispatch count. Supplemental contracts pin the §1.6 callback exception-surfacing (callback_test.dart — a thrown error surfaces verbatim at the call site and the engine stays usable), the complete across-a-reopen map decode (mapkeys_test.dart), and the frozen 0..19 error table (errcodes_test.dart). The fixtures are vendored in the repo and byte-compared against the release’s copies at fetch time, so a bad artifact is a loud fetch failure, never a silent skip.

On top sits docs/SURFACE.tsv — every construct of the engine’s public surface (327 rows at this pin) resolved to the Dart API exposing it plus the golden line that proves it, or N/A with the ABI’s §9 reason, gated in CI (scripts/surface-gate.sh). A fourth CI job regenerates the ffigen layer (lib/src/bindings.dart) from the freshly fetched header and byte-diffs the commit, so an engine pin bump can never change the ABI surface silently.

Next: corvid-js.