Sign in

    mooncakes-graph

    The mooncakes.io registry as a dependency graph: what depends on what, ranked by direct, transitive and other-owner dependents, with a tree(1)-like CLI.

    mooncakes
    dependency-graph
    dependents
    cli
    Download zip
    Author
    Version
    0.1.1
    License
    Apache-2.0
    Last updated
    3 hours ago
    Downloads
    25

    #gaato/mooncakes-graph

    The mooncakes.io registry as a dependency graph: what depends on what, and which modules the MoonBit ecosystem depends on the most. It is a library and a command-line tool that prints dependency trees like tree(1). Both read the registry index that moon update keeps, so they work offline.

    Unofficial; not affiliated with mooncakes.io or the MoonBit team. The same data as an interactive map: https://gaato.github.io/mooncakes-graph/

    #Install

    As a library:

    moon add gaato/mooncakes-graph

    The command-line tool runs with moonx without installing (see Run below), or installs into ~/.moon/bin:

    moon install gaato/mooncakes-graph/cmd/mooncakes-graph

    #Run

    Fetch the index with moon update, then ask what depends on a module:

    moon update moonx gaato/mooncakes-graph/cmd/mooncakes-graph dependents gaato/http

    commandoutput
    dependents MODULE...What depends on MODULE, as a tree like tree(1)
    deps MODULE...What MODULE depends on, as a tree
    top [-n N] [--by direct\|transitive\|owners]The most depended-on modules, as TSV
    statsCounts of modules, versions and edges, as JSON
    versions, modulesEvery version, and each module's description, as JSONL

    The trees take tree's -L, --noreport and -J. --help lists the rest.

    #Usage

    Build a graph from index records and rank the modules by how many depend on them:

    ///|
    test "rank modules by their dependents" {
    let graph = @graph.Graph::from_records([
    @mooncakes_index.VersionRecord::new("a/core", version="0.1.0"),
    @mooncakes_index.VersionRecord::new("b/app", version="0.1.0", deps=[
    @mooncakes_index.Dep::new("a/core", version="0.1.0"),
    ]),
    @mooncakes_index.VersionRecord::new("c/tool", version="0.2.0", deps=[
    @mooncakes_index.Dep::new("b/app", version="0.1.0"),
    ]),
    ])
    let metrics = graph.metrics()
    let core = graph.find("a/core").unwrap()
    assert_eq(metrics.get(core, Direct), 1)
    assert_eq(metrics.get(core, Transitive), 2)
    assert_eq(metrics.ranking(Transitive).map(n => graph.name(n)), [
    "a/core", "b/app", "c/tool",
    ])
    }

    For the real registry, read the index with gaato/mooncakes_index/fs and pass its modules to Graph::new: @graph.Graph::new(@fs.read(@fs.index_dir().unwrap()).modules).

    Graph follows each module's current version (its last version that is not yanked). metrics counts direct, transitive and other-owner dependents; forest builds the trees the CLI prints; requirement tells which version one module asks of another and whether that is from an older release line.

    #License

    Apache-2.0. See LICENSE.

    Direction

    pub(all) enum Direction {
    Dependents
    Deps
    } derive(Eq,
    Debug
    )

    Which way to follow dependencies.

    Direction::equal

    fn Direction::equal(Direction, Direction) -> Bool

    Exposes equal and not_equal as methods of Direction.

    Direction::not_equal

    fn Direction::not_equal(x : Direction, y : Direction) -> Bool

    Exposes equal and not_equal as methods of Direction.

    Direction::to_repr

    Exposes to_repr as a method of Direction, for debug output.

    Forest

    pub struct Forest {
    roots : ArrayView[TreeNode]
    modules : Int
    }

    Trees of what depends on (or what is depended on by) some modules, laid out the way tree(1) lays out directories.

    Graph

    type Graph

    The mooncakes.io registry as a dependency graph, at the current version of every module (@mooncakes_index.Module::current).

    Nodes are numbered 0..<size() in module-name order (@mooncakes_index.compare_names); use find to get a module's node. An edge from a to b means the current version of a depends on b. Dependencies on modules missing from the index, and on the module itself, are not edges.

    test {
    let g = @graph.Graph::from_records([
    @mooncakes_index.VersionRecord::new("a/core", version="0.1.0"),
    @mooncakes_index.VersionRecord::new("b/app", version="0.1.0", deps=[
    @mooncakes_index.Dep::new("a/core", version="0.1.0"),
    ]),
    ])
    let core = g.find("a/core").unwrap()
    assert_eq(g.dependents(core).map(n => g.name(n)), ["b/app"])
    }

    Graph::all_yanked

    fn Graph::all_yanked(self : Graph, node : Int) -> Bool

    Whether every version of the module is yanked.

    Graph::current

    The version that stands for the module: its last version not yanked, or its last version when every one is.

    Graph::dependents

    fn Graph::dependents(self : Graph, node : Int) -> ArrayView[Int]

    What depends on the module, in name order.

    Graph::deps

    fn Graph::deps(self : Graph, node : Int) -> ArrayView[Int]

    What the module's current version depends on, in the order it lists them.

    Graph::find

    fn Graph::find(self : Graph, name : StringView) -> Int?

    The node of the module named name, if the graph has it.

    Graph::forest

    fn Graph::forest(self : Graph, roots : ArrayView[Int], direction : Direction, max_depth? : Int) -> Forest

    The trees from roots in direction, in pre-order. Each module is expanded once, at its first appearance; later appearances, and cycles back to an ancestor, are marked duplicate. Below max_depth levels a node with neighbours is marked truncated instead of expanded; a max_depth of zero or less truncates the roots themselves.

    test {
    let g = @graph.Graph::from_records([
    @mooncakes_index.VersionRecord::new("a/core", version="0.1.0"),
    @mooncakes_index.VersionRecord::new("b/app", version="0.1.0", deps=[
    @mooncakes_index.Dep::new("a/core", version="0.1.0"),
    ]),
    ])
    let f = g.forest([g.find("a/core").unwrap()], Dependents)
    assert_eq(f.modules, 1)
    assert_eq(g.name(f.roots[0].children[0].node), "b/app")
    }

    Graph::from_records

    Builds the graph from version records in publication order per module, as the CLI's versions writes them.

    Graph::history

    Every version of the module, in publication order.

    Graph::isolated

    fn Graph::isolated(self : Graph, node : Int, excluded? : (Int) -> Bool) -> Bool

    Whether the module has no dependency, and no dependent, outside excluded.

    Graph::metrics

    fn Graph::metrics(self : Graph, excluded? : (Int) -> Bool) -> Metrics

    The metrics of every module. An excluded module scores zero and does not count, or carry counts, as a dependent of anything else.

    Graph::name

    fn Graph::name(self : Graph, node : Int) -> String

    The module's full name, such as moonbitlang/x, of node node.

    Graph::new

    Builds the graph from the modules of the index. Modules without versions and the registry's test modules are left out.

    Graph::owner

    fn Graph::owner(self : Graph, node : Int) -> String

    The owner part of the module's name (moonbitlang in moonbitlang/x).

    Graph::reachable

    fn Graph::reachable(self : Graph, node : Int, direction : Direction, excluded? : (Int) -> Bool) -> Array[Int]

    Every module reached from node in direction, directly or through others, nearest first; node itself is not included. Paths through an excluded module are cut.

    Graph::requirement

    fn Graph::requirement(self : Graph, dependent~ : Int, dependency~ : Int) -> Requirement?

    How dependent's current version asks for dependency, if it does.

    test {
    let g = @graph.Graph::from_records([
    @mooncakes_index.VersionRecord::new("a/core", version="0.1.0"),
    @mooncakes_index.VersionRecord::new("a/core", version="0.2.0"),
    @mooncakes_index.VersionRecord::new("b/app", version="0.1.0", deps=[
    @mooncakes_index.Dep::new("a/core", version="0.1.0"),
    ]),
    ])
    let r = g
    .requirement(
    dependent=g.find("b/app").unwrap(),
    dependency=g.find("a/core").unwrap(),
    )
    .unwrap()
    assert_eq(r.asked, Some("0.1.0"))
    assert_true(r.behind)
    }

    Graph::requirements

    fn Graph::requirements(self : Graph, node : Int) -> Array[Requirement]

    Every dependency of the module's current version, in the order it lists them, including ones the index does not have.

    Graph::size

    fn Graph::size(self : Graph) -> Int

    The number of modules, so nodes are 0..<size().

    Graph::stats

    fn Graph::stats(self : Graph) -> Stats

    Counts of the graph's modules, versions and dependencies.

    Metric

    pub(all) enum Metric {
    Direct
    Transitive
    Owners
    } derive(Eq,
    Debug
    )

    A way to count how much a module is depended on.

    Metric::equal

    fn Metric::equal(Metric, Metric) -> Bool

    Exposes equal and not_equal as methods of Metric.

    Metric::not_equal

    fn Metric::not_equal(x : Metric, y : Metric) -> Bool

    Exposes equal and not_equal as methods of Metric.

    Metric::to_repr

    Exposes to_repr as a method of Metric, for debug output.

    Metrics

    type Metrics

    The three metrics for every module of one graph.

    Metrics::get

    fn Metrics::get(self : Metrics, node : Int, metric : Metric) -> Int

    How many dependents node node has by metric.

    Metrics::ranking

    fn Metrics::ranking(self : Metrics, metric : Metric, among? : ArrayView[Int]) -> Array[Int]

    Modules from most to least depended on by metric, ties in name order: every module, or only those among.

    test {
    let g = @graph.Graph::from_records([
    @mooncakes_index.VersionRecord::new("a/core", version="0.1.0"),
    @mooncakes_index.VersionRecord::new("b/app", version="0.1.0", deps=[
    @mooncakes_index.Dep::new("a/core", version="0.1.0"),
    ]),
    ])
    let top = g.metrics().ranking(Transitive)
    assert_eq(top.map(n => g.name(n)), ["a/core", "b/app"])
    }

    Requirement

    pub struct Requirement {
    name : String
    node : Int?
    asked : String?
    behind : Bool
    yanked : Bool
    } derive(Eq,
    Debug
    )

    One dependency of a module's current version, as it asks for it.

    Requirement::equal

    fn Requirement::equal(Requirement, Requirement) -> Bool

    Exposes equal and not_equal as methods of Requirement.

    Requirement::not_equal

    fn Requirement::not_equal(x : Requirement, y : Requirement) -> Bool

    Exposes equal and not_equal as methods of Requirement.

    Requirement::to_repr

    Exposes to_repr as a method of Requirement, for debug output.

    Stats

    pub struct Stats {
    modules : Int
    versions : Int
    modules_with_deps : Int
    edges : Int
    dangling_edges : Int
    all_yanked : Int
    } derive(Eq,
    Debug
    )

    The registry at a glance.

    Stats::equal

    fn Stats::equal(Stats, Stats) -> Bool

    Exposes equal and not_equal as methods of Stats.

    Stats::not_equal

    fn Stats::not_equal(x : Stats, y : Stats) -> Bool

    Exposes equal and not_equal as methods of Stats.

    Stats::to_repr

    Exposes to_repr as a method of Stats, for debug output.

    TreeNode

    pub struct TreeNode {
    node : Int
    children : ArrayView[TreeNode]
    duplicate : Bool
    truncated : Bool
    }

    One module in a Forest.

    Powered by MoonBit

    Site sourceReport issuePackagesBuild queueSkillsStatistics

    © 2026 mooncakes.io