moonpropertydb

    An embedded property graph database written in MoonBit.

    Download zip
    Author
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    last month
    Downloads
    26

    #MoonPropertyDB

    MoonPropertyDB is an embedded property-graph database library written in MoonBit. It is designed for local application data without a separate database server.

    #Status

    The current development snapshot implements a tested graph and persistence slice:

    • typed node and edge IDs and scalar property values;
    • node and directed-edge CRUD with endpoint validation;
    • incoming/outgoing adjacency indexes;
    • node-label and edge-type indexes;
    • node- and edge-property equality indexes;
    • a single-writer, detached-candidate transaction boundary;
    • file-backed Database::init(path) and Database::open(path);
    • async commits that append one checksummed WAL record and synchronize it before publishing the candidate graph;
    • sequential WAL replay, index rebuilding, incomplete-tail repair, and an exclusive advisory writer lock;
    • structured errors and 51 native tests.

    This is an early persistent slice, not the complete v0.1 database. Snapshots, checkpoints, the query lexer/parser/planner/executor, the CLI, and the complete dependency-graph demo remain planned. The WAL is append-only and loaded fully into memory during open; there is no compaction yet. The current reader accepts WAL files below 2 GiB. The file lock is advisory and coordinates cooperating processes that use moonbitlang/async.

    #Scope and non-goals

    The approved v0.1 design targets a small reusable MoonBit API, scalar graph data, deterministic in-memory indexes, single-writer atomic transactions, and eventual versioned persistence. It does not target full Neo4j/openCypher compatibility, a database server, multi-writer concurrency, MVCC, distributed transactions, range/full-text/vector indexes, or a production database replacement.

    The design and status records are in:

    #Requirements and checks

    Install the current MoonBit CLI from the official instructions, then run from the repository root:

    moon check --target native moon test --target native moon build --target native moon fmt --check moon info --target native

    The local toolchain snapshot is Moon 0.1.20260920 with moonc 0.10.14+7d59c7ec9 (compiler release v0.10.14). The CI workflow repeats the native check, test, build, format, and interface-generation checks.

    #Runnable in-memory example

    The example creates two package nodes, connects them with a directed DEPENDS_ON edge, commits the transaction, reads the committed node, and closes the in-memory database:

    moon run examples/in_memory_graph --target native

    This example is intentionally in-memory. It does not imply persistence or recovery support.

    #Library API example

    The current public API can be used directly from MoonBit code:

    let database = @moonpropertydb.Database::new_in_memory()
    let transaction = try! database.begin_write()
    let package_a = try! transaction.create_node(["Package"], {
    "name": @moonpropertydb.PropertyValue::String("app"),
    })
    let package_b = try! transaction.create_node(["Package"], {})
    let _dependency = try! transaction.create_edge(
    package_a,
    package_b,
    "DEPENDS_ON",
    {},
    )
    try! transaction.commit()
    let committed = try! database.get_node(package_a)
    println("committed labels: \{committed.labels.length()}")
    try! database.close()

    The public interface is generated by moon info; pkg.generated.mbti is a generated review artifact and should not be edited manually.

    #CLI, persistence, and query language

    The CLI commands query, index create, stats, verify, and checkpoint, plus JSONL import/export, are not implemented in this snapshot. The library's Database::init(path) and Database::open(path) APIs are implemented for native filesystems.

    The current storage layer uses a versioned, checksummed append-only log and replays committed graph operations when opening a database. Snapshots and checkpointing remain planned. The planned query language is a bounded read-only property-graph subset, not full openCypher. See the design notes for the explicit boundaries and recovery invariants.

    #Tests and source provenance

    Tests cover scalar values, graph mutations, adjacency, secondary indexes, transaction lifecycle, rollback, structured failures, persistent reopen and replay, incomplete-tail repair, checksum corruption, and writer locking. Snapshots, query execution, and CLI tests will be added with those features.

    MoonPropertyDB is an original MoonBit implementation for this repository. It uses the Apache-2.0 moonbitlang/async package for native filesystem I/O and file locking. The standard moonbitlang/core packages are supplied by the MoonBit toolchain. See source and license notes before adding dependencies or generated/test data.

    #License

    MoonPropertyDB is distributed under the Apache License 2.0.

    Properties

    type Properties = Map[String, PropertyValue]

    A string-keyed map of supported scalar properties.

    DatabaseError

    pub(all) suberror DatabaseError {
    DatabaseDirectoryNotFound(path~ : String)
    DatabaseNotInitialized(path~ : String)
    DatabaseAlreadyInitialized(path~ : String)
    DatabaseClosed
    DatabaseRecoveryRequired
    IncompatibleDatabaseFormat(version~ : UInt16)
    WriterLockOccupied(path~ : String)
    WriterTransactionActive
    NodeNotFound(NodeId)
    NodeIdExhausted
    EdgeNotFound(EdgeId)
    EdgeIdExhausted
    EndpointNodeNotFound(NodeId)
    NodeHasIncidentEdges(NodeId)
    InvalidPropertyValue(property~ : String, reason~ : String)
    IndexAlreadyExists(name~ : String)
    IndexNotFound(name~ : String)
    QueryLexicalError(position~ : SourcePosition, reason~ : String)
    QuerySyntaxError(position~ : SourcePosition, reason~ : String)
    QuerySemanticError(position~ : SourcePosition, reason~ : String)
    IncompleteLogRecord(file~ : String, offset~ : UInt64, transaction_id~ : UInt64?)
    LogChecksumFailed(file~ : String, offset~ : UInt64, transaction_id~ : UInt64?)
    CorruptLogRecord(file~ : String, offset~ : UInt64, transaction_id~ : UInt64?, reason~ : String)
    SnapshotChecksumFailed(file~ : String, offset~ : UInt64, transaction_id~ : UInt64?)
    CorruptSnapshot(file~ : String, offset~ : UInt64, transaction_id~ : UInt64?, reason~ : String)
    IoError(file~ : String, operation~ : String, reason~ : String)
    TransactionFailed
    TransactionCommitInProgress
    TransactionAlreadyCommitted
    TransactionAlreadyRolledBack
    CommitOutcomeUnknown
    TransactionIdExhausted
    } derive(Eq,
    Debug
    )

    A typed, inspectable failure from a MoonPropertyDB public operation.

    Database

    pub struct Database {
    // private fields
    }

    An in-memory database handle over one committed graph state.

    Database::begin_write

    fn Database::begin_write(self : Database) -> WriteTransaction raise DatabaseError

    Begin the one constructor-facing write transaction allowed by this handle.

    Database::close

    fn Database::close(self : Database) -> Unit raise DatabaseError

    Close the handle and discard the constructor-facing active transaction.

    Database::get_edge

    fn Database::get_edge(self : Database, id : EdgeId) -> Edge raise DatabaseError

    Read a detached edge from the last committed state.

    Database::get_node

    fn Database::get_node(self : Database, id : NodeId) -> Node raise DatabaseError

    Read a detached node from the last committed state.

    Database::init

    async fn Database::init(path : String) -> Database raise DatabaseError

    Initialize a persistent database directory and hold its writer lock. The immediate parent directory must already exist.

    Database::new_in_memory

    fn Database::new_in_memory() -> Database

    Create an empty in-memory database.

    Database::open

    async fn Database::open(path : String) -> Database raise DatabaseError

    Open an initialized persistent database, replay its WAL and hold the writer lock.

    Edge

    pub(all) struct Edge {
    id : EdgeId
    from : NodeId
    to : NodeId
    edge_type : String
    properties : Map[String, PropertyValue]
    } derive(Eq,
    Debug
    )

    A detached directed edge value. The constructor retains the supplied properties by reference.

    Edge::Edge

    fn Edge::Edge(id~ : EdgeId, from~ : NodeId, to~ : NodeId, edge_type~ : String, properties~ : Map[String, PropertyValue]) -> Edge

    Construct a detached directed edge value.

    EdgeId

    pub(all) struct EdgeId(UInt64) derive(Eq, Hash,
    Debug
    )

    An immutable database-assigned edge identifier.

    Node

    pub(all) struct Node {
    id : NodeId
    labels : Array[String]
    properties : Map[String, PropertyValue]
    } derive(Eq,
    Debug
    )

    A detached node value. Graph/database operations own relationship invariants. The constructor retains the supplied labels and properties collections by reference.

    Node::Node

    fn Node::Node(id~ : NodeId, labels~ : Array[String], properties~ : Map[String, PropertyValue]) -> Node

    Construct a detached node value.

    NodeId

    pub(all) struct NodeId(UInt64) derive(Eq, Hash,
    Debug
    )

    An immutable database-assigned node identifier.

    PropertyValue

    pub(all) enum PropertyValue {
    Bool(Bool)
    Int64(Int64)
    Float64(Double)
    String(String)
    Bytes(Bytes)
    } derive(Eq, Hash,
    Debug
    )

    A supported scalar stored in node and edge properties. Database write boundaries must reject non-finite Float64 values and normalize negative zero; this enum itself can represent values that are invalid for storage.

    SourcePosition

    pub(all) struct SourcePosition {
    offset : UInt64
    line : Int
    column : Int
    } derive(Eq,
    Debug
    )

    A source location. Offset is a zero-based UTF-8 byte count, line is one-based, and column is a one-based Unicode scalar-value count (not grapheme clusters).

    SourcePosition::SourcePosition

    fn SourcePosition::SourcePosition(offset~ : UInt64, line~ : Int, column~ : Int) -> SourcePosition

    Construct a source location value.

    WriteTransaction

    pub struct WriteTransaction {
    // private fields
    }

    A write transaction over one detached candidate graph.

    WriteTransaction::add_label

    fn WriteTransaction::add_label(self : WriteTransaction, id : NodeId, label : String) -> Unit raise DatabaseError

    Add a label to a candidate node.

    WriteTransaction::commit

    async fn WriteTransaction::commit(self : WriteTransaction) -> Unit raise DatabaseError

    Persist and publish the detached candidate as one atomic transaction.

    WriteTransaction::create_edge

    fn WriteTransaction::create_edge(self : WriteTransaction, from : NodeId, to : NodeId, edge_type : String, properties : Map[String, PropertyValue]) -> EdgeId raise DatabaseError

    Create a directed edge in the detached candidate graph.

    WriteTransaction::create_edge_property_index

    fn WriteTransaction::create_edge_property_index(self : WriteTransaction, edge_type : String, property : String) -> Unit raise DatabaseError

    Define a candidate edge-property equality index.

    WriteTransaction::create_node

    fn WriteTransaction::create_node(self : WriteTransaction, labels : Array[String], properties : Map[String, PropertyValue]) -> NodeId raise DatabaseError

    Create a node in the detached candidate graph.

    WriteTransaction::create_node_property_index

    fn WriteTransaction::create_node_property_index(self : WriteTransaction, label : String, property : String) -> Unit raise DatabaseError

    Define a candidate node-property equality index.

    WriteTransaction::delete_edge

    fn WriteTransaction::delete_edge(self : WriteTransaction, id : EdgeId) -> Unit raise DatabaseError

    Delete a candidate edge.

    WriteTransaction::delete_node

    fn WriteTransaction::delete_node(self : WriteTransaction, id : NodeId) -> Unit raise DatabaseError

    Delete a candidate node only when it has no incident edges.

    WriteTransaction::delete_node_cascade

    fn WriteTransaction::delete_node_cascade(self : WriteTransaction, id : NodeId) -> Unit raise DatabaseError

    Delete a candidate node and its incident edges.

    WriteTransaction::edge_ids_by_property_index

    fn WriteTransaction::edge_ids_by_property_index(self : WriteTransaction, edge_type : String, property : String, value : PropertyValue) -> Array[EdgeId]? raise DatabaseError

    Return IDs from a candidate edge-property equality index.

    WriteTransaction::edge_ids_with_type

    fn WriteTransaction::edge_ids_with_type(self : WriteTransaction, edge_type : String) -> Array[EdgeId] raise DatabaseError

    Return candidate edge IDs carrying an edge type.

    WriteTransaction::get_edge

    fn WriteTransaction::get_edge(self : WriteTransaction, id : EdgeId) -> Edge raise DatabaseError

    Read a detached edge from the candidate graph.

    WriteTransaction::get_node

    fn WriteTransaction::get_node(self : WriteTransaction, id : NodeId) -> Node raise DatabaseError

    Read a detached node from the candidate graph.

    WriteTransaction::incoming_edges

    fn WriteTransaction::incoming_edges(self : WriteTransaction, node_id : NodeId) -> Array[Edge] raise DatabaseError

    Return detached candidate edges entering a node.

    WriteTransaction::node_ids_by_property_index

    fn WriteTransaction::node_ids_by_property_index(self : WriteTransaction, label : String, property : String, value : PropertyValue) -> Array[NodeId]? raise DatabaseError

    Return IDs from a candidate node-property equality index.

    WriteTransaction::node_ids_with_label

    fn WriteTransaction::node_ids_with_label(self : WriteTransaction, label : String) -> Array[NodeId] raise DatabaseError

    Return candidate node IDs carrying a label.

    WriteTransaction::outgoing_edges

    fn WriteTransaction::outgoing_edges(self : WriteTransaction, node_id : NodeId) -> Array[Edge] raise DatabaseError

    Return detached candidate edges leaving a node.

    WriteTransaction::remove_edge_property

    fn WriteTransaction::remove_edge_property(self : WriteTransaction, id : EdgeId, key : String) -> Unit raise DatabaseError

    Remove a candidate edge property.

    WriteTransaction::remove_label

    fn WriteTransaction::remove_label(self : WriteTransaction, id : NodeId, label : String) -> Unit raise DatabaseError

    Remove a label from a candidate node.

    WriteTransaction::remove_node_property

    fn WriteTransaction::remove_node_property(self : WriteTransaction, id : NodeId, key : String) -> Unit raise DatabaseError

    Remove a candidate node property.

    WriteTransaction::rollback

    fn WriteTransaction::rollback(self : WriteTransaction) -> Unit raise DatabaseError

    Discard the detached candidate and release the database writer slot. Failed transactions may only be recovered through rollback.

    WriteTransaction::set_edge_property

    fn WriteTransaction::set_edge_property(self : WriteTransaction, id : EdgeId, key : String, value : PropertyValue) -> Unit raise DatabaseError

    Set or replace a candidate edge property.

    WriteTransaction::set_node_property

    fn WriteTransaction::set_node_property(self : WriteTransaction, id : NodeId, key : String, value : PropertyValue) -> Unit raise DatabaseError

    Set or replace a candidate node property.