Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ladybug-fs

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.

Layout

/
├── 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).

Install

uv sync

Usage

# 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.

Semantics notes

  • Tables whose primary key is SERIAL and that have a STRING column (name preferred) list nodes by name; every other table lists nodes by primary-key value. Numeric dirs always resolve by PK as well, and _id exposes the raw id. Duplicate names disambiguate as <name>~<pk>.
  • A property file exists iff the property is non-NULL; cat of 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 -s requires the link name to equal the target's node name, and mv on a node dir renames the node itself.
  • Writes are applied on FLUSH (i.e. before close() returns), so a subsequent open() 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 with EROFS.

Development

uv sync --group dev
uv run pytest
uv run ruff check src tests

License

MIT

About

FUSE filesystem view over a LadybugDB graph database

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages