Implementing a binding¶
Choose the boundary by implementation language:
A Rust-side wrapper (PyO3, Bolt, MCP, a Rust service) calls
kglite::api::*directly.A non-Rust wrapper (cgo, napi, JNI, P/Invoke, Swift FFI) calls the supported
kglite-cABI.
Precompiled C ABI libraries are not currently attached to releases; non-Rust
bindings must build and package the matching kglite-c library for each target.
Do not bind internal graph::*/datatypes::* modules or recreate the query
pipeline in the wrapper.
Boundary principle¶
Core owns synchronous, binding-independent behavior: graph lifecycle, storage configuration, typed errors, the session/transaction state machine, the canonical Cypher pipeline, and embedder registration. Wrappers own runtime idioms: async scheduling, wire/value conversion, display/error formatting, iteration/chunking, logging, teardown, authentication, and transport.
Cypher is the first choice for query-expressible features. Add direct API only for lifecycle/configuration/registration work Cypher cannot express. Read the full boundary principle before adding a surface.
Rust-side wrapper¶
use kglite::api::{io::load_file, session::{execute_read, ExecuteOptions}};
use std::collections::HashMap;
let graph = load_file("graph.kgl")?;
let params = HashMap::new();
let opts = ExecuteOptions::eager(¶ms);
let outcome = execute_read(&graph, "MATCH (n) RETURN count(n)", &opts)?;
Use api::session::{execute_read, execute_mut} so parsing, schema validation,
optimizer passes, budgets, cancellation, write scope, and provenance remain
identical across wrappers. Use Session/Transaction when failed mutations
must not publish.
ExecuteOptions::cancel has the lifetime required by the public signature; do
not hand it an ordinary request-local atomic. A wrapper can enforce its own
request deadline or own a cancellation flag with a sufficiently long lifetime.
RDF ingestion is feature-gated and lives under
kglite::api::io::{load_rdf, load_ntriples}. Code-graph construction belongs
to codingest; link its own documentation rather than promising an unverified
function signature here.
Non-Rust wrapper¶
Include the generated header and use only exported functions:
#include "kglite.h"
KgliteGraph *graph = NULL;
const char *error = NULL;
KgliteStatusCode status = kglite_load_file("graph.kgl", &graph, &error);
if (status != KGLITE_STATUS_CODE_OK) {
/* copy/map error, then */
kglite_free_string(error);
}
Wrap opaque handles with deterministic close/free plus a defensive
finalizer. Every returned Rust string uses kglite_free_string; every graph,
session, result, and embedder uses its matching free function. Decode result
JSON before freeing its owning result handle.
ABI v1 supports sessions, read/mutation options, and atomic mutation batches.
It does not expose explicit transaction begin/commit calls. Model explicit
transactions only after the ABI grows a real handle contract; never invent
kglite_session_begin in wrapper code.
Values and errors¶
Rust wrappers map all Value variants explicitly, including Timestamp, List,
Map, Node, Relationship, and Path. Non-Rust wrappers decode the JSON result
shape supplied by kglite-c.
Preserve KgErrorCode/KgliteStatusCode and the human message separately.
Map all 17 engine codes, including cancellation, plus the C-boundary codes for
invalid UTF-8/null pointers. Do not infer categories from message text.
Storage and persistence¶
Expose memory/mapped/disk choice without changing semantics. The current .kgl
writer emits RGF v6 with an explicit Postcard codec; the reader also accepts
RGF v5. RGF v4/bincode and older containers are rejected with
migration/rebuild guidance. A binding
must not serialize internal structs independently or promise compatibility
broader than the core reader.
For cross-package handoff, the declared consumer version floor must read the format written by the linked engine. Verify the exact artifact in a clean consumer package, not only from the workspace source tree.
Surface checklist¶
Lifecycle: create/open/load/save/bytes/free and storage configuration.
Query: canonical session pipeline, parameters, budgets, typed results.
Transactions/concurrency: snapshots, serialized shared writes, OCC mapping.
Errors: stable codes, messages, null/UTF-8/allocation failures.
Optional capabilities: embedder, RDF, blueprint, schema/introspection.
Documentation: copy-paste install and first-query example for the target.
Avoid mirroring every Python convenience method. Cypher exposes algorithms, procedures, spatial/temporal/text functions, and most per-query features to all bindings without a larger ABI.
Verification¶
Compile a downstream consumer against the packaged crate/header/library.
Exercise create/query/mutate/save/reload and every owned-result cleanup path.
Test malformed UTF-8/null pointers for FFI wrappers.
Test concurrent reads, serialized writes, conflicts, deadlines, row budgets, cancellation, and read-only rejection.
Run storage parity and format round trips for memory/mapped/disk.
Diff the Rust public API or generated C header in CI; update the baseline only for an intentional reviewed change.
See API reference, Session, Embedding, and C ABI.