A FUSE filesystem view over a LadybugDB
graph database. Mount the graph as a directory tree and use everyday shell
tools (ls, cat, mkdir, rm, …) to read and write graph data.
/
├── tables.txt # list of node tables
├── tables/ # directory of node tables
│ └── <TableName>/
│ ├── _schema # JSON: column types + primary key (read-only)
│ ├── _count # text: number of nodes (read-only)
│ └── <name>/ # directory per node (display name; SERIAL tables)
│ ├── _id # the auto-assigned numeric id (read-only)
│ ├── <prop> # one file per property (read/write)
│ └── <Rel>/ # one dir per outgoing rel type ...
│ └── <name> -> ../../../<Tbl>/<name> # symlinks to neighbors
├── rels.txt # list of relationship tables
├── rels/ # directory of relationship tables
│ └── <RelName>/
│ ├── _schema # JSON: from/to + property types (read-only)
│ ├── _count # text: number of relationships (read-only)
│ ├── .new # write {"from": "Label:id", "to": "Label:id", ...} to create a rel
│ └── <table:offset>/# directory per relationship
│ ├── _id # internal rel id (read-only)
│ ├── _from # "<SrcTable>:<src_id>" (read-only)
│ ├── _to # "<DstTable>:<dst_id>" (read-only)
│ └── <prop> # one file per property (read/write)
└── .create/ # write JSON payloads here to create tables
├── tables/<Name> # {"columns": {"id*": "INT64", "name": "STRING"}}
└── rels/<Name> # {"from": "A", "to": "B", "columns": {...}}
Reads return graph state; writes translate to Cypher (CREATE NODE TABLE,
MERGE … SET, ALTER TABLE … ADD, MATCH … DELETE, DROP TABLE).
uv sync# Mount daemonizes by default and exits cleanly, printing the pid and log.
# Mount an in-memory database
uv run ladybug-fs mount /tmp/lbfs --new
# Or open (or create) a persistent database
uv run ladybug-fs mount /tmp/lbfs --db mydb.lbdb
# Check health, find logs, unmount
uv run ladybug-fs status
uv run ladybug-fs status /tmp/lbfs
uv run ladybug-fs unmount /tmp/lbfs
# Stay attached instead (blocks until Ctrl-C); --debug for FUSE traces
uv run ladybug-fs mount /tmp/lbfs --db mydb.lbdb --foreground --debug
# Populate it from the shell.
# mkdir on /tables/<T> creates a node table (``id SERIAL PRIMARY KEY``
# plus a ``name`` column by default; use /.create/tables/<T> with a JSON
# payload for a fully-specified schema).
mkdir /tmp/lbfs/tables/Person
mkdir /tmp/lbfs/tables/Person/Alice /tmp/lbfs/tables/Person/Bob
cat /tmp/lbfs/tables/Person/Alice/_id # auto-assigned numeric id
echo -n '30' > /tmp/lbfs/tables/Person/Alice/age
# Writing a property for a missing node creates the node, and writing a
# brand-new property name adds the column (type is inferred).
echo -n 'golf' > /tmp/lbfs/tables/Person/Alice/hobby
ls /tmp/lbfs/tables/Person/
cat /tmp/lbfs/tables/Person/_schema
cat /tmp/lbfs/tables/Person/Alice/name
# Relationships are symlinks between node dirs: ln -s creates an edge,
# rm deletes it, plain reads follow the link to the neighbor node.
echo '{"from": "Person", "to": "Person"}' > /tmp/lbfs/.create/rels/Knows
ln -s ../../../Person/Bob /tmp/lbfs/tables/Person/Alice/Knows/Bob
ls -l /tmp/lbfs/tables/Person/Alice/Knows/
cat /tmp/lbfs/tables/Person/Alice/Knows/Bob/name
# The same edge can also be created with a JSON payload (Label:name
# endpoints; extra keys become relationship properties).
# echo '{"from": "Person:Alice", "to": "Person:Bob", "since": 2020}' \
# > /tmp/lbfs/rels/Knows/.new
# Renaming a node dir renames the node; rm on a link deletes the edge.
mv /tmp/lbfs/tables/Person/Bob /tmp/lbfs/tables/Person/Robert
rm /tmp/lbfs/tables/Person/Alice/Knows/Robert
# Deletion: rm a property file clears it (NULL), rmdir on a node/rel
# directory deletes it, rmdir on a table directory drops the table.
rm /tmp/lbfs/tables/Person/Alice/age
rmdir /tmp/lbfs/tables/Person/Alice
# Unmount
uv run ladybug-fs unmount /tmp/lbfs
# (or plain fusermount -u /tmp/lbfs)Mount state (pid, db, log path) lives in
~/.local/state/ladybug-fs/mounts.json ($XDG_STATE_HOME respected,
$LADYBUG_FS_STATE_DIR overrides); daemon logs default to
~/.local/state/ladybug-fs/logs/<mountpoint>.log (--log-file overrides).
status prints the log path for every mount and auto-prunes stale records.
Run uv run ladybug-fs --help for the full CLI.
- Tables whose primary key is
SERIALand that have aSTRINGcolumn (namepreferred) list nodes by name; every other table lists nodes by primary-key value. Numeric dirs always resolve by PK as well, and_idexposes the raw id. Duplicate names disambiguate as<name>~<pk>. - A property file exists iff the property is non-NULL;
catof an all-NULL property reports "No such file or directory", like a deleted file. - A property literally named like a rel type keeps working as a file
(properties win); the rel links stay available under
/rels. - Rel symlink targets are relative (
../../../<Table>/<name>);ln -srequires the link name to equal the target's node name, andmvon a node dir renames the node itself. - Writes are applied on
FLUSH(i.e. beforeclose()returns), so a subsequentopen()always sees the previous write. Opening a file writable without writing anything (e.g.touch) changes nothing. - Mount with
--read-only(or-o ro) to reject all writes withEROFS.
uv sync --group dev
uv run pytest
uv run ruff check src testsMIT