Dart API Reference¶
Complete reference for the grafeo Dart package, native bindings for Grafeo via dart:ffi. Wraps the grafeo-c shared library for high-performance FFI access.
Installation¶
Add the package from pub.dev:
The package requires the grafeo_c shared library at runtime. Place libgrafeo_c.so (Linux), libgrafeo_c.dylib (macOS) or grafeo_c.dll (Windows) in a location visible to your application. You can also pass a custom path via the libraryPath parameter on factory constructors. Every GitHub release has the library for each desktop platform, in a default and an all features variant (see prebuilt libraries).
Quick Start¶
import 'package:grafeo/grafeo.dart';
void main() {
final db = GrafeoDB.memory();
db.execute('''
INSERT (:Person {name: "Alix", age: 30}),
(:Person {name: "Gus", age: 28})
''');
db.execute('''
MATCH (a:Person {name: "Alix"}), (b:Person {name: "Gus"})
INSERT (a)-[:KNOWS]->(b)
''');
final result = db.execute('''
MATCH (a:Person)-[r:KNOWS]->(b:Person)
RETURN a.name AS from, b.name AS to
''');
for (final row in result.rows) {
print('${row['from']} knows ${row['to']}');
}
// Output: Alix knows Gus
db.close();
}
GrafeoDB¶
The main database class. All methods are synchronous (FFI calls block the current isolate).
Lifecycle¶
GrafeoDB.memory()¶
Create a new in-memory database.
GrafeoDB.open()¶
Open a persistent database at the given path.
GrafeoDB.openSingleFile()¶
Deprecated since 0.6.0 and removed in 0.7.0: use open(). Opens or creates a persistent database at the given path (a single file), with a sidecar WAL for crash safety. Since 0.6 every database is a single file, whatever the extension of its path, so this is the same as open().
GrafeoDB.openReadOnly()¶
Open an existing database in read-only mode. Multiple read-only handles may be opened concurrently on the same path. Write operations throw DatabaseException.
close()¶
Close the database, flushing all writes. Safe to call multiple times. After closing, all other methods throw a DatabaseException.
version()¶
Returns the grafeo-c library version string.
Query Execution¶
execute()¶
Execute a GQL (ISO standard) query and return a QueryResult.
final result = db.execute('MATCH (p:Person) RETURN p.name, p.age');
for (final row in result.rows) {
print('${row['p.name']}: ${row['p.age']}');
}
executeWithParams()¶
Execute a GQL query with named parameters. Parameters are JSON-encoded using the grafeo-bindings-common wire format. Temporal types (DateTime, Duration) are automatically encoded.
final result = db.executeWithParams(
'MATCH (p:Person) WHERE p.age > \$minAge RETURN p.name',
{'minAge': 25},
);
executeCypher()¶
Execute a Cypher query. Requires the cypher feature in grafeo-c.
executeSparql()¶
Execute a SPARQL query. Requires the sparql feature in grafeo-c.
executeGremlin()¶
Execute a Gremlin query. Requires the gremlin feature in grafeo-c.
executeGraphql()¶
Execute a GraphQL query. Requires the graphql feature in grafeo-c.
executeCypherWithParams()¶
Execute a Cypher query with named parameters.
executeGremlinWithParams()¶
Execute a Gremlin query with named parameters.
executeGraphqlWithParams()¶
Execute a GraphQL query with named parameters.
executeSparqlWithParams()¶
Execute a SPARQL query with named parameters.
executeLanguage()¶
Execute a query in any supported language with optional parameters. The language parameter is one of: "gql", "cypher", "gremlin", "graphql", "sparql", "sql".
final result = db.executeLanguage('cypher', 'MATCH (n) RETURN n LIMIT 10');
final result2 = db.executeLanguage('gql',
r'MATCH (p:Person) WHERE p.age > $min RETURN p',
params: {'min': 25},
);
Statistics¶
| Member | Type | Description |
|---|---|---|
nodeCount | int (getter) | Number of nodes in the database (O(1)) |
edgeCount | int (getter) | Number of edges in the database (O(1)) |
info()¶
Returns high-level database information as a parsed JSON map.
Schema Context¶
setSchema()¶
Set the active schema for subsequent queries. Equivalent to SESSION SET SCHEMA in GQL.
resetSchema()¶
Clear the active schema, reverting to the default graph store.
currentSchema()¶
Return the currently active schema name, or null if none is set.
db.setSchema('analytics');
print(db.currentSchema()); // 'analytics'
db.resetSchema();
print(db.currentSchema()); // null
Transactions¶
beginTransaction()¶
Begin a new transaction with the default isolation level.
final tx = db.beginTransaction();
try {
tx.execute('INSERT (:Person {name: "Vincent"})');
tx.commit();
} catch (_) {
tx.rollback();
rethrow;
}
beginTransactionWithIsolation()¶
Begin a transaction with a specific isolation level.
Node CRUD¶
createNode()¶
Create a node with labels and properties. Returns the new node ID.
getNode()¶
Get a node by ID. Throws on error if the node does not exist.
final node = db.getNode(0);
print(node.labels); // ['Person']
print(node.properties); // {'name': 'Alix', 'age': 30}
getNodeLabels()¶
Return the labels of a node without fetching full node data. More efficient than getNode() when only labels are needed.
deleteNode()¶
Delete a node by ID. Returns true if the node existed.
detachDeleteNode()¶
Delete a node with all its edges, as DETACH DELETE does. Returns true if the node existed; the node and its edges are one change.
setNodeProperty()¶
Set a property on a node.
removeNodeProperty()¶
Remove a property from a node.
addNodeLabel()¶
Add a label to an existing node.
removeNodeLabel()¶
Remove a label from a node.
Edge CRUD¶
createEdge()¶
Create an edge from a source node to a target node with a type and properties. Returns the new edge ID.
getEdge()¶
Get an edge by ID. Throws on error if the edge does not exist.
final edge = db.getEdge(0);
print(edge.type); // 'KNOWS'
print(edge.sourceId); // 0
print(edge.targetId); // 1
getNodeEdges()¶
A node's edges without a query, as NodeEdge(neighborId, edgeId). direction is Direction.out (the default), Direction.incoming or Direction.both (which lists a self-loop once); edgeTypes keeps only edges of those types. A node that does not exist has none; a GrafeoGraph has the same method for its graph.
deleteEdge()¶
Delete an edge by ID. Returns true if the edge existed.
setEdgeProperty()¶
Set a property on an edge.
removeEdgeProperty()¶
Remove a property from an edge.
Property Indexes¶
createPropertyIndex()¶
Create a property index for fast point-lookup queries.
dropPropertyIndex()¶
Drop a property index. Returns true if it existed.
hasPropertyIndex()¶
Check if a property index exists.
findNodesByProperty()¶
Find all node IDs where the given property equals the given value. Requires a property index.
db.createPropertyIndex('name');
final ids = db.findNodesByProperty('name', 'Alix');
for (final id in ids) {
final node = db.getNode(id);
print(node.properties);
}
Vector Search¶
createVectorIndex()¶
Create an HNSW vector index on nodes with the given label.
void createVectorIndex(
String label,
String property,
int dimensions,
String metric, {
int m = 16,
int efConstruction = 200,
})
| Parameter | Description |
|---|---|
label | Node label to index |
property | Property name containing the float vector |
dimensions | Vector dimensions (e.g. 384, 768, 1536) |
metric | Distance metric: "cosine", "euclidean", or "dot" |
m | Bidirectional links per HNSW node (default: 16) |
efConstruction | Index build quality (default: 200) |
vectorSearch()¶
Perform a k-nearest-neighbour vector search. For diversity-aware results, use mmrSearch() instead.
List<VectorResult> vectorSearch(
String label,
String property,
List<double> query, {
required int k,
int ef = 64,
})
final results = db.vectorSearch('Document', 'embedding', queryVec, k: 10);
for (final r in results) {
print('Node ${r.nodeId}: distance ${r.distance}');
}
batchCreateNodes()¶
Bulk-create nodes with vector embeddings. All nodes get the same label; each receives a unique embedding vector stored under the given property.
final vectors = [
[0.1, 0.2, 0.3],
[0.4, 0.5, 0.6],
[0.7, 0.8, 0.9],
];
final ids = db.batchCreateNodes('Document', 'embedding', vectors);
print(ids); // [0, 1, 2]
Requires vector-index feature
batchCreateNodes is only available when grafeo-c is built with --features vector-index (included in the ai and analytics profiles).
batchVectorSearch()¶
Search several query vectors of one dimension at once, with optional property filters. Returns one list per query, nearest first.
List<List<VectorResult>> batchVectorSearch(
String label,
String property,
List<List<double>> queries, {
required int k,
int ef = 0,
Map<String, dynamic>? filters,
})
mmrSearch()¶
Perform a Maximal Marginal Relevance (MMR) vector search for diverse nearest neighbors.
List<VectorResult> mmrSearch(
String label,
String property,
List<double> query, {
required int k,
required int fetchK,
required double lambda,
required int ef,
})
| Parameter | Description |
|---|---|
label | Node label to search |
property | Vector property name |
query | Query vector as a list of doubles |
k | Number of results to return |
fetchK | Number of candidates to fetch before reranking |
lambda | Balance between relevance (1.0) and diversity (0.0) |
ef | Search beam width (higher is more accurate but slower) |
final results = db.mmrSearch('Document', 'embedding', queryVec,
k: 5, fetchK: 20, lambda: 0.7, ef: 64);
for (final r in results) {
print('Node ${r.nodeId}: distance ${r.distance}');
}
dropVectorIndex()¶
Drop a vector index. Returns true if the index existed; throws a DatabaseException when the database refuses the change.
rebuildVectorIndex()¶
Rebuild a vector index by rescanning all matching nodes.
Batches and Upserts¶
// One transaction each; IDs in input order
final ids = db.batchCreateNodesWithProps(['Person'], [
{'name': 'Alix'},
{'name': 'Gus'},
]);
final edgeIds = db.batchCreateEdges([BatchEdge(ids[0], ids[1], 'KNOWS')]);
// Create or update by a key property: created, updated and skipped rows
final summary = db.upsertNodes(['File'], [{'path': 'a.rs'}], key: 'path');
db.upsertEdges('IMPORTS', [{'id': 'a-b', 'src': 'a.rs', 'dst': 'b.rs'}],
endpointKey: 'path', endpointLabels: ['File']);
An edge to a missing node fails the edge batch and writes nothing. A node row without the key is skipped, and so is an edge row whose endpoint matches no node or several; endpoints are never created.
Named Graphs¶
db.graph(name) returns a GrafeoGraph that works in one named graph without switching the current graph: execute, executeWithParams, executeLanguage, the node and edge methods, the batches and the upserts. Once the graph is dropped, every call throws. close() frees it.
final trips = db.graph('trips');
final prague = trips.createNode(['City'], {'name': 'Prague'});
final result = trips.execute('MATCH (c:City) RETURN c.name');
trips.close();
Time Travel¶
Reads of the database as it was at an earlier epoch or time. The historyRetention option of GrafeoDB.memory and GrafeoDB.open sets how much history it keeps: HistoryRetention.off (the default), HistoryRetention.forever or HistoryRetention.duration(Duration); a database file stores the setting. A point is a TimePoint: TimePoint.epoch(int), TimePoint.at(DateTime) (the last commit at or before it; a local and a UTC DateTime name one instant) or TimePoint.ago(Duration). A point before the history the database keeps throws ("not retained"). A view keeps the history of its epoch until close(); besides execute it has getNode, getEdge and getNodeProperty, which give null when the entity or the property did not exist then.
final db = GrafeoDB.memory(
historyRetention: HistoryRetention.duration(const Duration(days: 7)));
final then = db.currentEpoch;
final view = db.asOf(TimePoint.ago(const Duration(minutes: 5)));
final result = view.execute('MATCH (p:Person) RETURN count(p) AS n');
view.close();
db.executeAsOf('MATCH (p:Person) RETURN count(p) AS n', TimePoint.epoch(then));
db.epochAsOf(TimePoint.at(DateTime.utc(2026, 3, 1, 11)));
Change History¶
With db.cdcEnabled = true, every committed change is recorded as a ChangeEvent (kind, epoch, properties before and after, labels, the endpoints of an edge, and deletedWith on an edge a detach delete removed).
final events = db.nodeHistory(id);
db.edgeHistory(edgeId);
db.nodeHistorySince(id, epoch);
db.changesBetween(startEpoch, endEpoch);
Admin¶
save()¶
Save a database snapshot to the given path.
walCheckpoint()¶
Force a write-ahead log checkpoint, flushing buffered writes to the main data file.
setCheckpointTriggers()¶
Let the database checkpoint by itself: every interval, and once the WAL holds walBytes bytes since the last checkpoint. Leave both out to turn automatic checkpoints off (the default). For this open only; an in-memory or read-only database refuses them.
checkpointStatus()¶
How the automatic checkpoints fare: interval_ms and wal_bytes (the triggers, null when off), last_checkpoint, failures (automatic checkpoints that failed since the last that succeeded), last_error, last_failure and stopped.
Transaction¶
An ACID transaction on a Grafeo database. Obtain via GrafeoDB.beginTransaction() or GrafeoDB.beginTransactionWithIsolation(). Must be explicitly committed or rolled back. If dropped without either, the Rust Drop implementation performs an automatic rollback.
Transaction.execute()¶
Execute a GQL query within this transaction.
Transaction.executeWithParams()¶
Execute a parameterized GQL query within this transaction.
Transaction.executeLanguage()¶
Execute a query in any supported language within this transaction.
final tx = db.beginTransaction();
tx.executeLanguage('cypher', "CREATE (n:Person {name: 'Alix'})");
tx.commit();
commit()¶
Commit the transaction, making all changes permanent.
rollback()¶
Roll back the transaction, discarding all changes. After a commit that failed it does nothing: that commit ended the transaction, and another commit() throws.
Example¶
final tx = db.beginTransaction();
try {
tx.execute("INSERT (:Person {name: 'Alix'})");
tx.execute("INSERT (:Person {name: 'Gus'})");
final result = tx.executeWithParams(
'MATCH (p:Person) WHERE p.name = \$name RETURN p',
{'name': 'Alix'},
);
print(result.rows.length); // 1
tx.commit();
} catch (e) {
tx.rollback();
rethrow;
}
IsolationLevel¶
Transaction isolation levels, matching the C GrafeoStatus enum codes.
| Value | Code | Description |
|---|---|---|
readCommitted | 0 | Reads see only committed data; no dirty reads |
snapshotIsolation | 1 | Reads use a consistent snapshot taken at transaction start |
serializable | 2 | Not available yet: refused for now: snapshot isolation is the strongest level until queries record their reads |
Data Types¶
GrafeoDecimal¶
An exact decimal from Grafeo (GQL 1.50, CAST(x AS DECIMAL(10, 2))): up to 38 digits, kept as its text with its scale. Equal values are equal whatever their scale (1.50 equals 1.5). Dart has no decimal type of its own: toDouble() gives the nearest double, and GrafeoDecimal('1.50') passed as a parameter goes in exactly.
final result = db.execute('RETURN 19.99 AS price');
final price = result.rows.first['price'] as GrafeoDecimal;
print(price.text); // 19.99
Dates, times and durations¶
A GQL TIMESTAMP is a UTC DateTime. The temporal values Dart has no type for without loss come back as small immutable types, and each goes in again as the same GQL type:
GrafeoDate(DATE):year,month,day;GrafeoDate.fromDateTime,toDateTime().GrafeoTime(TIME):hour,minute,second,nanosecondandoffset(null for a local time).GrafeoDuration(DURATION):months,daysandnanoseconds, kept apart because a month and a day have no fixed length (P1Mis notP30D,P1Dis notPT24H);toDuration()gives a DartDurationwhen there are no months and no part finer than a microsecond (otherwise null), andGrafeoDuration.fromDurationmakes one of aDuration.GrafeoZonedDateTime(ZONED DATETIME): theinstant(a UTCDateTime) and theoffsetit was written at.
Each has text (the ISO text GQL writes), parse and tryParse.
final row = db.execute(
"RETURN DATE '2024-03-05' AS day, TIME '12:30:00+02:00' AS opens").rows.first;
final day = row['day'] as GrafeoDate; // 2024-03-05
final opens = row['opens'] as GrafeoTime; // 12:30:00+02:00, offset 2:00:00
db.executeWithParams(r'RETURN $t IS TYPED TIME AS typed', {'t': opens}); // true
RdfTerm¶
An RDF term from a SPARQL result: its type (RdfTermType.iri, blankNode or literal), its value (the IRI, the blank node's label without _:, or the lexical form), and for a literal its language tag or datatype (null for a simple literal; literalDatatype gives xsd:string or rdf:langString there). An RdfTerm passed as a parameter, or stored as a property, is that exact term. Language tags compare without case, so a term keeps its tag in lowercase (language: 'FR' is 'fr'), and a literal of datatype xsd:string is the simple literal: two RdfTerms are equal when they are the same RDF term. A map with type and value keys stays a map: on the wire a term is the $term tag (see the C API). A result's kind is ResultKind.rows, ResultKind.boolean for a SPARQL ASK (boolean gives its answer) or ResultKind.graph for CONSTRUCT and DESCRIBE.
final result = db.executeSparql('SELECT ?o WHERE { <http://ex.org/apple> <http://ex.org/label> ?o }');
final label = result.rows.first['o'] as RdfTerm;
print('${label.value} ${label.language}'); // Pomme fr
Node¶
A graph node with an ID, labels and properties.
Equality is based on id only.
Edge¶
A graph edge with an ID, type, source/target endpoints and properties.
class Edge {
final int id;
final String type;
final int sourceId;
final int targetId;
final Map<String, dynamic> properties;
}
Equality is based on id only.
QueryResult¶
The result of a query execution.
class QueryResult {
final List<String> columns;
final List<Map<String, dynamic>> rows;
final List<Node> nodes;
final List<Edge> edges;
final double executionTimeMs;
final int rowsScanned;
final WriteCounters counters;
}
| Field | Description |
|---|---|
columns | Column names extracted from the first row |
rows | List of row maps with column-name keys |
nodes | Deduplicated Node entities found in the result rows |
edges | Deduplicated Edge entities found in the result rows |
executionTimeMs | Query execution time in milliseconds |
rowsScanned | Number of rows scanned during execution |
counters | What a write changed: nodesCreated, nodesDeleted, edgesCreated, edgesDeleted, propertiesSet, labelsAdded, labelsRemoved (all zero for a read) |
VectorResult¶
A single vector search result with a node ID and distance score.
Exception Hierarchy¶
All Grafeo errors extend the sealed base class GrafeoException, which carries the error's GQLSTATUS in gqlStatus ('22012' for a division by zero, see Error Codes; null for an exception the binding raises itself). Use pattern matching to handle specific error types:
try {
db.execute('INVALID QUERY');
} on QueryException catch (e) {
print('Query error: ${e.message}');
} on TransactionException catch (e) {
print('Transaction error: ${e.message}');
} on GrafeoException catch (e) {
print('Grafeo error (${e.status.name}): ${e.message}');
}
Class Hierarchy¶
GrafeoException (sealed)
+-- QueryException // query parsing or execution error
+-- TransactionException // conflict or invalid transaction state
+-- StorageException // storage or IO error
+-- SerializationException // data serialization error
+-- DatabaseException // generic database error (catch-all)
Every exception carries a message string and a GrafeoStatus enum value.
GrafeoStatus¶
Status codes returned by grafeo-c FFI functions, matching the C enum exactly.
enum GrafeoStatus {
ok(0),
database(1),
query(2),
transaction(3),
storage(4),
io(5),
serialization(6),
internal(7),
nullPointer(8),
invalidUtf8(9);
}
Type Mapping¶
| Dart | Grafeo | Notes |
|---|---|---|
null | Null | |
bool | Bool | |
int | Int64 | |
BigInt | Int64, or Decimal past 64 bits | Exact up to 38 digits (scale 0); past that an error naming the parameter |
double | Float64 | |
String | String | |
DateTime | Timestamp (LOCAL DATETIME) | To the microsecond, in UTC; comes back as a UTC DateTime |
GrafeoZonedDateTime | Zoned datetime | An instant with its offset |
GrafeoDate | Date | |
GrafeoTime | Time | With an offset or without one |
Duration, GrafeoDuration | Duration | A Duration is one of its microseconds; every duration comes back as a GrafeoDuration |
List | List | Elements converted recursively |
Map<String, dynamic> | Map | Keys must be strings |
Uint8List | Bytes | Encoded as base64 |
Float32List | Vector | For embeddings and similarity search |
Float64List | Vector | Converted to list of doubles |
NativeFinalizer¶
Both GrafeoDB and Transaction implement Finalizable and register a NativeFinalizer for automatic cleanup. If you forget to call close() on a database or commit()/rollback() on a transaction, the Dart garbage collector will invoke the native destructor to prevent resource leaks.
You should still call close() and commit()/rollback() explicitly for deterministic resource management. The finalizer is a safety net, not a substitute for proper lifecycle handling.
// Recommended: explicit cleanup
final db = GrafeoDB.memory();
try {
// ... use the database ...
} finally {
db.close();
}