moon-neo4j

    A pure-MoonBit Neo4j client: Bolt protocol, PackStream codec, HTTP transactional endpoint, and a typed Cypher query builder.

    neo4j
    graph-database
    property-graph
    knowledge-graph
    bolt
    cypher
    client
    query-builder
    Download zip
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    5 hours ago
    Downloads
    1

    #moon-neo4j

    CI mooncakes.io

    A pure-MoonBit Neo4j client: a Bolt protocol implementation, the PackStream serialization codec, an HTTP transactional-endpoint client, and a typed Cypher query builder — written from scratch in MoonBit with zero runtime dependencies.

    #Install

    moon add Rz-coder8848/moon-neo4j

    Then import the package (the alias is the last path segment):

    import { "Rz-coder8848/moon-neo4j" @lib }

    #What's inside

    LayerWhat it doesEntry points
    PackStream codecvalue model + binary encoder/decoderPackStreamValue, packstream_encode, packstream_decode
    Bolt transportbyte pipe abstraction + in-memory mockTransport, MockTransport
    Bolt handshakemagic 0x6060B017 + version negotiationbolt_version, handshake_message, parse_handshake_response
    Bolt messagesmessage signatures, builders, parser, chunked framinghello, run, begin, commit, rollback, pull, frame, unframe, parse_message
    Sessionconnection state machineBoltConnection, ConnState
    Transactionsexplicit BEGIN / COMMIT / ROLLBACKTransaction
    Cypher buildertyped, injection-safe query constructionQuery
    HTTP endpoint/db/{database}/tx request/response codecbuild_tx_request, parse_tx_response, tx_commit, value_to_json, value_from_json
    Demorunnable Matrix movie-graph demodemo_actors, demo_add_person

    #Quick start

    #Typed Cypher builder (pure — no connection)

    let q = @lib.Query::new()
    let title = q.param(PackStreamValue::str("The Matrix"))
    q.match_("(p:Person)-[:ACTED_IN]->(m:Movie)")
    q.where_("m.title = " + title)
    q.return_("p.name")
    q.order_by("p.name")
    let (cypher, params) = q.build()
    // cypher == "MATCH (p:Person)-[:ACTED_IN]->(m:Movie) WHERE m.title = $p0 RETURN p.name ORDER BY p.name"
    // params == [("p0", PackStreamValue::str("The Matrix"))]

    Runtime values enter only through Query::param, which stores them out-of-band under a generated $pN name — Cypher injection is impossible by construction.

    #Bolt session

    let conn = BoltConnection::new(transport) // transport : Transport
    let _ = conn.handshake([bolt_version(5, 1, 0)])
    let _ = conn.authenticate([("user_agent", PackStreamValue::str("my-app/1.0"))])
    let (cypher, params) = q.build()
    match conn.run_query(cypher, params) {
    Some(rows) => println("rows: \{rows.length()}")
    None => println("query failed")
    }

    #Explicit transaction

    let tx = Transaction::new(conn)
    if tx.begin() {
    let _ = tx.run("CREATE (n:Person {name: \$p0})", [("p0", PackStreamValue::str("Lana"))])
    let _ = tx.commit()
    }

    #HTTP transactional endpoint

    let resp = tx_commit(
    client, // client : HttpClient
    "http://localhost:7474/db/neo4j/tx/commit",
    [Statement::new("RETURN 1", [])],
    )

    #Demo CLI

    The package ships a runnable Matrix demo:

    moon run cmd/main # query actors of "The Matrix", then add a person in a transaction moon run cmd/main add Lana # run only the "add a person" demo

    #Status: the transport boundary

    The entire protocol layer — PackStream codec, Bolt message state machine, typed Cypher builder, transactions, and the HTTP endpoint codec — is implemented and fully tested without any network access. Every Bolt byte is exercised against an in-memory [MockTransport], and the HTTP endpoint against an in-memory [HttpClient].

    MoonBit's core library does not yet ship a socket or HTTP client, so the concrete networking backend lives behind the [Transport] and [HttpClient] traits and is target-platform-specific (需查官方文档). Drop in a TCP-backed Transport (or a fetch-backed HttpClient on wasm) and the rest of the stack works unchanged.

    #Testing

    83 tests pass with zero warnings:

    moon check # type-checks clean moon test # 83 whitebox + blackbox tests moon fmt # formats the code

    The whitebox tests (*_wbtest.mbt) cover every PackStream marker and every state-machine transition; the blackbox tests (*_test.mbt) drive the public API the way an external consumer would.

    #Project layout

    . ├── packstream.mbt # PackStream value model + codec ├── stream.mbt # streaming reader/writer ├── message.mbt # Bolt message codec + chunked framing ├── handshake.mbt # Bolt handshake (magic + version negotiation) ├── transport.mbt # Transport trait + MockTransport ├── session.mbt # BoltConnection state machine ├── transaction.mbt # explicit transactions ├── cypher.mbt # typed Cypher query builder ├── http.mbt # HTTP transactional endpoint codec ├── demo.mbt # Matrix movie-graph demo ├── cmd/main/ # demo CLI (executable package) └── moon.mod / moon.pkg

    #Attribution

    This project is an independent reimplementation of the Neo4j Bolt and PackStream wire protocols; it is not a copy of any existing driver. Its design is informed by the public Neo4j protocol specifications and these reference drivers:

    #License

    HttpClient

    pub trait HttpClient {
    fn post_json(Self, String, Json) -> Json?
    }

    A minimal HTTP client used by the transactional endpoint.

    Transport

    pub trait Transport {
    fn write(Self, Bytes) -> Unit
    fn read_exact(Self, Int) -> Bytes?
    fn close(Self) -> Unit
    }

    A raw, byte-oriented transport for Bolt.

    BoltConnection

    pub struct BoltConnection[T] {
    transport : T
    state : ConnState
    }

    A Bolt session over a concrete transport.

    BoltConnection::authenticate

    fn[T : Transport] BoltConnection::authenticate(self : BoltConnection[T], metadata : Array[(String, PackStreamValue)]) -> ServerMessage?

    Send HELLO to authenticate the session. metadata carries the user agent and auth token (see the higher-level driver for how to build it).

    Returns the server's response: Success (session becomes Ready), or Failure (session becomes Failed). Returns None on an unexpected response or a dropped connection.

    BoltConnection::begin_tx

    fn[T : Transport] BoltConnection::begin_tx(self : BoltConnection[T], extra : Array[(String, PackStreamValue)]) -> ServerMessage?

    Send BEGIN to start an explicit transaction. The session must be Ready; on a server Success a transaction is open and the session stays Ready. Run statements inside the transaction with [BoltConnection::run_query], then finish with [BoltConnection::commit_tx] or [BoltConnection::rollback_tx].

    BoltConnection::close

    fn[T : Transport] BoltConnection::close(self : BoltConnection[T]) -> Unit

    Gracefully close the session: send GOODBYE (unless already failed or disconnected) and close the transport.

    BoltConnection::commit_tx

    fn[T : Transport] BoltConnection::commit_tx(self : BoltConnection[T]) -> ServerMessage?

    Send COMMIT to commit the open transaction. The session must be Ready; it stays Ready on a server Success.

    BoltConnection::conn_state

    fn[T] BoltConnection::conn_state(self : BoltConnection[T]) -> ConnState

    The session's current [ConnState].

    BoltConnection::handshake

    fn[T : Transport] BoltConnection::handshake(self : BoltConnection[T], versions : Array[Int]) -> Int?

    Perform the Bolt handshake, proposing versions in priority order.

    Returns the version the server agreed on, or None when the server rejected every proposal or the connection dropped. On success the session moves to Connected; on failure to Failed.

    BoltConnection::new

    fn[T] BoltConnection::new(transport : T) -> BoltConnection[T]

    BoltConnection::pull_next

    fn[T : Transport] BoltConnection::pull_next(self : BoltConnection[T], extra : Array[(String, PackStreamValue)]) -> ServerMessage?

    Send PULL and read the next server message: a Record, the stream's final Success (session returns to Ready), or a Failure.

    BoltConnection::reset_conn

    fn[T : Transport] BoltConnection::reset_conn(self : BoltConnection[T]) -> ServerMessage?

    Reset the session after a failure, returning it to Ready.

    BoltConnection::rollback_tx

    fn[T : Transport] BoltConnection::rollback_tx(self : BoltConnection[T]) -> ServerMessage?

    Send ROLLBACK to roll back the open transaction. The session must be Ready; it stays Ready on a server Success.

    BoltConnection::run_query

    fn[T : Transport] BoltConnection::run_query(self : BoltConnection[T], query : String, parameters : Array[(String, PackStreamValue)]) -> Array[Array[PackStreamValue]]?

    Convenience: run a query from the Ready state and pull every record, returning the rows (Array of field lists). The session is left Ready on success.

    BoltConnection::start_run

    fn[T : Transport] BoltConnection::start_run(self : BoltConnection[T], query : String, parameters : Array[(String, PackStreamValue)], extra : Array[(String, PackStreamValue)]) -> ServerMessage?

    Send RUN to begin evaluating a query. The session must be Ready; on a server Success it becomes Streaming.

    ConnState

    pub enum ConnState {
    Disconnected
    Connected
    Ready
    Streaming
    Failed
    Closed
    } derive(Eq,
    Debug
    )

    The lifecycle state of a Bolt session.

    MockHttpClient

    pub struct MockHttpClient {
    response : Json?
    last_body : Json?
    }

    An in-memory [HttpClient] for tests. The response is fixed up front and the request body is captured for inspection.

    MockHttpClient::last_body

    fn MockHttpClient::last_body(self : MockHttpClient) -> Json?

    The body of the most recent POST.

    MockHttpClient::new

    fn MockHttpClient::new(response : Json?) -> MockHttpClient

    MockTransport

    pub struct MockTransport {
    inbound :
    Buffer

    inpos : Int
    outbound :
    Buffer

    closed : Bool
    }

    An in-memory [Transport] used to test the protocol layer without a network. Tests pre-load the server's reply with [MockTransport::feed] and inspect what the client sent with [MockTransport::outgoing].

    MockTransport::close

    fn MockTransport::close(self : MockTransport) -> Unit

    Re-export the transport methods as regular methods on [MockTransport].

    MockTransport::feed

    fn MockTransport::feed(self : MockTransport, bytes : Bytes) -> Unit

    Append bytes the "server" will later send to the client.

    MockTransport::is_closed

    fn MockTransport::is_closed(self : MockTransport) -> Bool

    Whether [MockTransport::close] has been called.

    MockTransport::new

    MockTransport::outgoing

    fn MockTransport::outgoing(self : MockTransport) -> Bytes

    The bytes the client has written so far.

    MockTransport::read_exact

    fn MockTransport::read_exact(self : MockTransport, n : Int) -> Bytes?

    Re-export the transport methods as regular methods on [MockTransport].

    MockTransport::write

    fn MockTransport::write(self : MockTransport, bytes : Bytes) -> Unit

    Re-export the transport methods as regular methods on [MockTransport].

    PackStreamValue

    pub enum PackStreamValue {
    Null
    Bool(Bool)
    Int(Int64)
    Float(Double)
    Str(String)
    List(Array[PackStreamValue])
    Map(Array[(String, PackStreamValue)])
    Struct(Int, Array[PackStreamValue])
    Bytes(Bytes)
    } derive(Eq,
    Debug
    )

    A PackStream value. Maps are kept as an ordered list of (String, value) pairs so insertion order is preserved exactly as sent on the wire.

    PackStreamValue::bool

    fn PackStreamValue::bool(b : Bool) -> PackStreamValue

    PackStreamValue::bytes

    fn PackStreamValue::bytes(b : Bytes) -> PackStreamValue

    PackStreamValue::float

    fn PackStreamValue::float(d : Double) -> PackStreamValue

    PackStreamValue::int

    fn PackStreamValue::int(i : Int64) -> PackStreamValue

    PackStreamValue::int32

    fn PackStreamValue::int32(i : Int) -> PackStreamValue

    Convenience constructor for a 32-bit integer value.

    PackStreamValue::list

    PackStreamValue::map

    fn PackStreamValue::map(entries : Array[(String, PackStreamValue)]) -> PackStreamValue

    PackStreamValue::null

    PackStreamValue::str

    fn PackStreamValue::str(s : String) -> PackStreamValue

    PackStreamValue::struct_

    fn PackStreamValue::struct_(tag : Int, fields : Array[PackStreamValue]) -> PackStreamValue

    tag is the single-byte struct signature; it must be in 0..=255.

    PackStreamValue::to_string

    fn PackStreamValue::to_string(self : PackStreamValue) -> String

    Promote to_string to a regular method so value.to_string() needs no explicit Show qualification.

    Query

    pub struct Query {
    clauses : Array[String]
    params : Array[(String, PackStreamValue)]
    counter : Int
    }

    An in-progress Cypher query. The clauses are accumulated in order; the parameters are collected as (name, value) pairs keyed by the bare name (the $ reference in the query text resolves to this key on the server).

    Query::build

    fn Query::build(self : Query) -> (String, Array[(String, PackStreamValue)])

    Finalize the query into its (cypher, parameters) pair, ready for [BoltConnection::run_query] or http::Statement::new.

    Query::create

    fn Query::create(self : Query, pattern : String) -> Unit

    Append CREATE <pattern>.

    Query::delete

    fn Query::delete(self : Query, expression : String) -> Unit

    Append DELETE <expression>.

    Query::detach_delete

    fn Query::detach_delete(self : Query, expression : String) -> Unit

    Append DETACH DELETE <expression>.

    Query::limit

    fn Query::limit(self : Query, n : Int) -> Unit

    Append LIMIT <n>.

    Query::match_

    fn Query::match_(self : Query, pattern : String) -> Unit

    Append MATCH <pattern>.

    Query::merge

    fn Query::merge(self : Query, pattern : String) -> Unit

    Append MERGE <pattern>.

    Query::new

    fn Query::new() -> Query

    Start a new, empty query.

    Query::optional_match

    fn Query::optional_match(self : Query, pattern : String) -> Unit

    Append OPTIONAL MATCH <pattern>.

    Query::order_by

    fn Query::order_by(self : Query, expression : String) -> Unit

    Append ORDER BY <expression> (ascending). Use [Query::order_by_desc] for descending order.

    Query::order_by_desc

    fn Query::order_by_desc(self : Query, expression : String) -> Unit

    Append ORDER BY <expression> DESC.

    Query::param

    fn Query::param(self : Query, value : PackStreamValue) -> String

    Bind a runtime value and return its generated $pN reference. The value is stored in the query's parameter list under the bare name (without $), so it is sent out-of-band and cannot be interpreted as Cypher syntax.

    Query::return_

    fn Query::return_(self : Query, expression : String) -> Unit

    Append RETURN <expression>.

    Query::set

    fn Query::set(self : Query, assignments : String) -> Unit

    Append SET <assignments>.

    Query::skip

    fn Query::skip(self : Query, n : Int) -> Unit

    Append SKIP <n>.

    Query::where_

    fn Query::where_(self : Query, predicate : String) -> Unit

    Append WHERE <predicate>.

    Query::with_

    fn Query::with_(self : Query, projection : String) -> Unit

    Append WITH <projection>.

    Reader

    pub struct Reader {
    bytes : Bytes
    pos : Int
    }

    A PackStream reader over a byte buffer with a cursor.

    Reader::eof

    fn Reader::eof(self : Reader) -> Bool

    Whether the reader has consumed all of its input.

    Reader::new

    fn Reader::new(bytes : Bytes) -> Reader

    Reader::read

    fn Reader::read(self : Reader) -> PackStreamValue?

    Read the next value at the cursor, advancing past it. Returns None on malformed or truncated input (the cursor is left unchanged in that case).

    Reader::remaining

    fn Reader::remaining(self : Reader) -> Int

    Number of bytes left to consume.

    ResultSet

    pub struct ResultSet {
    columns : Array[String]
    rows : Array[Array[PackStreamValue]]
    } derive(Eq,
    Debug
    )

    One result set returned by the server.

    ServerMessage

    pub enum ServerMessage {
    Success(Array[(String, PackStreamValue)])
    Record(Array[PackStreamValue])
    Failure(Array[(String, PackStreamValue)])
    Ignored
    } derive(Eq,
    Debug
    )

    A message sent by the server in response to client messages.

    Statement

    pub struct Statement {
    statement : String
    parameters : Array[(String, PackStreamValue)]
    } derive(Eq,
    Debug
    )

    A single statement in a transactional request.

    Statement::new

    fn Statement::new(statement : String, parameters : Array[(String, PackStreamValue)]) -> Statement

    Transaction

    pub struct Transaction[T] {
    conn : BoltConnection[T]
    open : Bool
    }

    An explicit Bolt transaction bound to a [BoltConnection].

    Transaction::begin

    fn[T : Transport] Transaction::begin(self : Transaction[T]) -> Bool

    Begin the transaction with BEGIN. Returns true when the server accepted it, false otherwise (already open, wrong connection state, or a server failure).

    Transaction::commit

    fn[T : Transport] Transaction::commit(self : Transaction[T]) -> Bool

    Commit the transaction with COMMIT. Returns true on success; the transaction is then closed either way.

    Transaction::is_open

    fn[T] Transaction::is_open(self : Transaction[T]) -> Bool

    Whether the transaction is currently open.

    Transaction::new

    fn[T] Transaction::new(conn : BoltConnection[T]) -> Transaction[T]

    Wrap a connection in a (not yet started) transaction. The connection must already be handshaken and authenticated (in the Ready state).

    Transaction::rollback

    fn[T : Transport] Transaction::rollback(self : Transaction[T]) -> Bool

    Roll the transaction back with ROLLBACK. Returns true on success; the transaction is then closed either way.

    Transaction::run

    fn[T : Transport] Transaction::run(self : Transaction[T], query : String, parameters : Array[(String, PackStreamValue)]) -> Array[Array[PackStreamValue]]?

    Run a statement inside the transaction and pull every record, returning the rows. Returns None when the transaction is not open, the connection is not ready, or the server rejected the statement.

    TxError

    pub struct TxError {
    code : String
    message : String
    } derive(Eq,
    Debug
    )

    An error reported by the server.

    TxResponse

    pub struct TxResponse {
    results : Array[ResultSet]
    errors : Array[TxError]
    } derive(Eq,
    Debug
    )

    The parsed response of a transactional request.

    Writer

    pub struct Writer {
    buf :
    Buffer

    }

    A growable PackStream writer. Values are encoded onto an internal buffer and flushed with [Writer::to_bytes] once a whole message has been assembled.

    Writer::new

    fn Writer::new() -> Writer

    Writer::to_bytes

    fn Writer::to_bytes(self : Writer) -> Bytes

    Flush the accumulated bytes.

    Writer::write

    fn Writer::write(self : Writer, value : PackStreamValue) -> Unit

    Append value to the writer's buffer.

    MAGIC

    let MAGIC : Bytes

    The 4-byte magic preamble sent at the start of every Bolt connection.

    SIG_BEGIN

    let SIG_BEGIN : Int

    Client signature: BEGIN.

    SIG_COMMIT

    let SIG_COMMIT : Int

    Client signature: COMMIT.

    SIG_DISCARD

    let SIG_DISCARD : Int

    Client signature: DISCARD.

    SIG_FAILURE

    let SIG_FAILURE : Int

    Server signature: FAILURE.

    SIG_GOODBYE

    let SIG_GOODBYE : Int

    Client signature: GOODBYE.

    SIG_HELLO

    let SIG_HELLO : Int

    Client signature: HELLO.

    SIG_IGNORED

    let SIG_IGNORED : Int

    Server signature: IGNORED.

    SIG_PULL

    let SIG_PULL : Int

    Client signature: PULL.

    SIG_RECORD

    let SIG_RECORD : Int

    Server signature: RECORD.

    SIG_RESET

    let SIG_RESET : Int

    Client signature: RESET.

    SIG_ROLLBACK

    let SIG_ROLLBACK : Int

    Client signature: ROLLBACK.

    SIG_RUN

    let SIG_RUN : Int

    Client signature: RUN.

    SIG_SUCCESS

    let SIG_SUCCESS : Int

    Server signature: SUCCESS.

    actors_query

    fn actors_query(title : String) -> (String, Array[(String, PackStreamValue)])

    Build the "who acted in this movie" query.

    add_person_query

    fn add_person_query(name : String, born : Int64) -> (String, Array[(String, PackStreamValue)])

    Build the "add a person" query.

    begin

    fn begin(extra : Array[(String, PackStreamValue)]) -> PackStreamValue

    BEGIN: start an explicit transaction.

    bolt_version

    fn bolt_version(major : Int, minor : Int, range : Int) -> Int

    Encode a Bolt version (major, minor, range) into its 4-byte wire value.

    range is the number of consecutive minor versions below minor that are also accepted (0 = only the single major.minor).

    build_tx_request

    fn build_tx_request(statements : Array[Statement]) -> Json

    Build the JSON request body for a list of statements.

    commit

    fn commit() -> PackStreamValue

    COMMIT: commit the open transaction.

    demo_actors

    fn demo_actors(title : String) -> Unit

    Run the "actors in a movie" demo and print the result.

    demo_add_person

    fn demo_add_person(name : String, born : Int64) -> Unit

    Run the "add a person" demo (inside an explicit transaction) and print the result.

    discard

    fn discard(extra : Array[(String, PackStreamValue)]) -> PackStreamValue

    DISCARD: discard the remaining records of a result.

    frame

    fn frame(value : PackStreamValue) -> Bytes

    Frame a message struct into Bolt's chunked wire encoding: the PackStream payload split into ≤65535-byte chunks, each prefixed by a big-endian 16-bit length, terminated by a 0x0000 chunk.

    goodbye

    fn goodbye() -> PackStreamValue

    GOODBYE: gracefully close the connection.

    handshake_message

    fn handshake_message(versions : Array[Int]) -> Bytes

    Build the 20-byte handshake message: [MAGIC] followed by four version proposals, padded with 0x00000000 when fewer than four are given.

    Versions are sent in priority order; the server picks the first it supports.

    hello

    fn hello(metadata : Array[(String, PackStreamValue)]) -> PackStreamValue

    HELLO: initiate a session with connection metadata (user agent, auth, ...).

    packstream_decode

    fn packstream_decode(bytes : Bytes) -> PackStreamValue?

    Decode a single PackStream value from bytes.

    Returns None when the bytes do not hold exactly one well-formed value (truncated input, an unknown marker, or trailing garbage).

    packstream_encode

    fn packstream_encode(value : PackStreamValue) -> Bytes

    Encode a [PackStreamValue] to its PackStream byte representation.

    parse_handshake_response

    fn parse_handshake_response(bytes : Bytes) -> Int?

    Parse the server's 4-byte handshake response into an agreed version.

    Returns None when the response is not exactly 4 bytes, or when the server selected 0x00000000 ("no supported version").

    parse_message

    fn parse_message(value : PackStreamValue) -> ServerMessage?

    Parse a PackStream struct into a [ServerMessage], or None when the tag is unknown or the fields do not match the message's shape.

    parse_tx_response

    fn parse_tx_response(json : Json) -> TxResponse?

    Parse a transactional response body into a [TxResponse], or None when the JSON does not have the expected shape.

    pull

    fn pull(extra : Array[(String, PackStreamValue)]) -> PackStreamValue

    PULL: request the next records of a result.

    reset

    fn reset() -> PackStreamValue

    RESET: return the connection to a clean state, discarding any open transaction.

    rollback

    fn rollback() -> PackStreamValue

    ROLLBACK: roll back the open transaction.

    run

    fn run(query : String, parameters : Array[(String, PackStreamValue)], extra : Array[(String, PackStreamValue)]) -> PackStreamValue

    RUN: start a query/transaction. extra carries mode, bookmarks and transaction metadata.

    scripted_actors_server

    fn scripted_actors_server() -> MockTransport

    A scripted server whose replies answer the [actors_query] demo: it returns the three actors of The Matrix.

    scripted_add_server

    fn scripted_add_server(name : String) -> MockTransport

    A scripted server whose replies answer the [add_person_query] demo run inside an explicit transaction.

    tx_commit

    fn[T : HttpClient] tx_commit(client : T, url : String, statements : Array[Statement]) -> TxResponse?

    Commit a batch of statements and parse the response. Returns None on a transport failure or a malformed response.

    unframe

    fn unframe(bytes : Bytes) -> Bytes?

    Reassemble a chunked message into its PackStream payload bytes. Returns None on a truncated chunk or a missing 0x0000 terminator.

    value_from_json

    fn value_from_json(json : Json) -> PackStreamValue

    Convert a JSON value to its PackStream representation. JSON objects map to PackStream maps (nodes/relationships are not distinguished here).

    value_to_json

    fn value_to_json(value : PackStreamValue) -> Json

    Convert a PackStream value to its JSON representation. Structs and bytes have no JSON-native form and map to null; use Bolt for those.