Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

graph

An in-memory directed graph with an interactive REPL, a Cypher-inspired query language (GQL), JSON persistence, and an optional web UI.

Getting started

You need Zig 0.16. Build the binary:

zig build           # produces ./zig-out/bin/graph
zig build test      # run the unit and integration test suite

The graph binary has three modes:

1. Interactive REPL (default)

./zig-out/bin/graph

Starts immediately with a graph> prompt — no arguments or config needed. The graph starts empty and lives in memory for the session. Type help to list all commands, exit or Ctrl-D to quit. You can type both imperative commands and GQL queries at the prompt:

graph> node add name=Alice age=30
Added node 0
graph> node add name=Bob age=25
Added node 1
graph> edge add 0 1 type=friends
Added edge 0
graph> MATCH (n) WHERE n.age >= 28 RETURN n.name
name: "Alice"
graph> show
  0(Alice) --[friends]--> 1(Bob)
  1(Bob)
graph> exit

2. Run a GQL script

Pass -f <file.gql> to execute a file of ;-separated GQL statements and print the results:

cat > demo.gql <<'EOF'
CREATE (a:Person {name: "Alice", age: 30});
CREATE (b:Person {name: "Bob", age: 25});
MATCH (n:Person) RETURN n.name, n.age
EOF

./zig-out/bin/graph -f demo.gql

3. HTTP server + web UI

Start the backend HTTP server (loopback only, default port 7878):

./zig-out/bin/graph --serve         # or: --serve 9000 for a custom port

It exposes GET /graph (the graph as JSON) and POST /command (run a command/GQL line, returns {"output": "..."}). You can hit it directly:

curl -X POST --data 'node add name=Alice' http://127.0.0.1:7878/command
curl http://127.0.0.1:7878/graph

For the interactive visualization, run the React front end in a second terminal. It proxies /api/* to the server on port 7878, so start the backend first:

cd web
npm install
npm run dev          # open the printed http://localhost:5173 URL

Run the tests

zig build test       # unit + integration tests (see the Architecture section)

Architecture

The code is organized into small, single-responsibility modules under src/. Every file opens with a //! doc comment describing its role; the summary:

Module Responsibility
types.zig Core, dependency-free data types (NodeId, PropertyValue, Node, Edge).
graph.zig The in-memory directed property graph: nodes, edges, adjacency lists, secondary indexes.
traverse.zig Graph algorithms (BFS shortest path).
format.zig Shared rendering/parsing of property values for human-readable output.
json_io.zig JSON serialization/deserialization of the whole graph (shared by persist and server).
persist.zig File-backed save/load (thin filesystem wrapper over json_io).
commands.zig Handlers for the imperative REPL commands.
repl.zig Interactive loop and command/GQL dispatch.
gql.zig Public facade tying the GQL pipeline together.
gql_lexer.zig / gql_parser.zig / gql_ast.zig / gql_executor.zig The GQL lexer → parser → AST → executor pipeline.
server.zig Minimal HTTP server backing the web/ UI.
main.zig Executable entry point and CLI dispatch (-f, --serve, REPL).

Dependency direction flows downward: everything depends on types.zig; the REPL and server depend on commands/gql; nothing depends back up on main.

Tests

Tests live next to the code they cover (e.g. graph.zig, traverse.zig, format.zig, json_io.zig, persist.zig, commands.zig, repl.zig), with two cross-cutting suites: gql_test.zig (lexer/parser/executor) and integration_test.zig (end-to-end flows driven through repl.dispatch — imperative + GQL + persistence + JSON). zig build test runs them all.

REPL commands

Command Description
node add [k=v ...] Add a node with optional properties
node rm <id> Remove a node (cascades its edges)
node get <id> Show a node's properties
node list List all nodes
edge add <from> <to> [k=v ...] Add a directed edge
edge rm <id> Remove an edge
edge get <id> Show an edge's properties
edge list List all edges
neighbors <id> Show out- and in-neighbors of a node
path <from> <to> BFS shortest path between two nodes
show Print adjacency-list view of the graph
save <file> Serialize graph to a JSON file
load <file> Load graph from a JSON file (replaces current graph)
help Print command reference
exit / quit Exit the REPL

Property values are auto-typed: age=30 becomes a number, name=Alice becomes a string.

Examples

Build a small social graph

graph> node add name=Alice age=30
Added node 0
graph> node add name=Bob age=25
Added node 1
graph> node add name=Carol age=28
Added node 2
graph> edge add 0 1 type=friends since=2020
Added edge 0
graph> edge add 1 2 type=colleagues since=2019
Added edge 1
graph> edge add 0 2 type=follows since=2022
Added edge 2

Inspect the graph

graph> show
  0(Alice) --[friends]--> 1(Bob) --[follows]--> 2(Carol)
  1(Bob) --[colleagues]--> 2(Carol)
  2(Carol)

graph> node get 0
0: {name: "Alice", age: 30}

graph> edge list
0: 0 -> 1 {type: "friends", since: 2020}
1: 1 -> 2 {type: "colleagues", since: 2019}
2: 0 -> 2 {type: "follows", since: 2022}

Query neighbors and paths

graph> neighbors 1
  Out: 2(Carol)
  In:  0(Alice)

graph> path 0 2
0(Alice) -> 1(Bob) -> 2(Carol)

Modify the graph

graph> node rm 1
Removed node 1

graph> show
  0(Alice) --[follows]--> 2(Carol)
  2(Carol)

graph> path 0 2
0(Alice) -> 2(Carol)

Save and restore

graph> save social.json
Saved to social.json

graph> node rm 0
Removed node 0

graph> show
  2(Carol)

graph> load social.json
Loaded from social.json (2 nodes, 1 edges)

graph> show
  0(Alice) --[follows]--> 2(Carol)
  2(Carol)

Exit

graph> exit

Ctrl-D also exits cleanly.

GQL

The REPL also accepts a graph query language inspired by Cypher. Lines that start with MATCH or CREATE (case-insensitive) are dispatched to the GQL interpreter instead of the command parser.

Syntax reference

Pattern syntax

(variable)                      any node
(variable:Type)                 node of a specific type
(variable {prop: value})        node matching inline properties
(variable:Type {prop: value})   type and inline properties combined

-[variable]->                   outgoing edge (any type)
-[variable:Type]->              outgoing edge of a specific type
<-[variable]-                   incoming edge
<-[variable:Type]-              incoming edge of a specific type

CREATE

CREATE (n)
CREATE (n:Type)
CREATE (n:Type {key: "value", num: 42, flag: true})
CREATE (a:Type)-[:EdgeType]->(b:Type)
CREATE (a:Type)<-[:EdgeType]-(b:Type)

MATCH … RETURN

MATCH (n) RETURN n
MATCH (n:Person) RETURN n
MATCH (n:Person {name: "Alice"}) RETURN n
MATCH (n) RETURN n.name, n.age
MATCH (n) RETURN n.name AS name, n.age AS age
MATCH (a)-[e:KNOWS]->(b) RETURN a, e, b
MATCH (a)<-[e:FOLLOWS]-(b) RETURN b

MATCH … WHERE … RETURN

WHERE accepts =, <>, <, >, <=, >= comparisons on property access expressions, combined with AND, OR, and NOT.

MATCH (n) WHERE n.age > 25 RETURN n
MATCH (n) WHERE n.name = "Alice" AND n.age >= 30 RETURN n.name
MATCH (n) WHERE n.role = "admin" OR n.role = "mod" RETURN n
MATCH (n) WHERE NOT n.active = false RETURN n

MATCH … DELETE

Deletes all matched nodes (cascades incident edges) or edges.

MATCH (n) DELETE n
MATCH (n) WHERE n.age < 18 DELETE n
MATCH (a)-[e:SPAM]->(b) DELETE e

MATCH … SET

Updates properties on matched nodes.

MATCH (n) SET n.active = true
MATCH (n) WHERE n.name = "Alice" SET n.age = 31, n.verified = true

GQL examples

Create a social graph

graph> CREATE (a:Person {name: "Alice", age: 30})
Created node 0
graph> CREATE (b:Person {name: "Bob", age: 25})
Created node 1
graph> CREATE (c:Person {name: "Carol", age: 28})
Created node 2
graph> CREATE (a:Person {name: "Alice"})-[:FRIENDS]->(b:Person {name: "Bob"})
Created nodes 3, 4 and edge 0

Query nodes

graph> MATCH (n:Person) RETURN n.name, n.age
name: "Alice", age: 30
name: "Bob", age: 25
name: "Carol", age: 28

graph> MATCH (n) WHERE n.age >= 28 RETURN n.name AS name
name: "Alice"
name: "Carol"

Traverse edges

graph> MATCH (a)-[e:FRIENDS]->(b) RETURN a, e, b
a: (0:Person {name: "Alice", age: 30}), e: [0:FRIENDS 0->1], b: (1:Person {name: "Bob", age: 25})

Update properties

graph> MATCH (n) WHERE n.name = "Bob" SET n.age = 26
graph> MATCH (n) WHERE n.name = "Bob" RETURN n.age
age: 26

Delete with a filter

graph> MATCH (n) WHERE n.age < 27 DELETE n
graph> MATCH (n:Person) RETURN n.name
name: "Alice"
name: "Carol"

Data model

  • Nodes have an auto-assigned integer ID and an optional property map (string or number values).
  • Edges are directed, have an auto-assigned integer ID, a from node, a to node, and an optional property map.
  • Removing a node cascades — all of its incident edges are removed automatically.
  • The graph is in-memory only; use save/load for persistence.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages