npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hologit/holo-tree

v0.5.0

Published

Node.js native binding for holo-tree — mutable in-memory git trees via gix

Downloads

97

Readme

@hologit/holo-tree

Git tree, ref, blob, and commit primitives for Node.js — a native binding over the holo-tree Rust crate, powered by gitoxide.

  • No git binary. Everything runs in-process through gix; nothing shells out. Works anywhere Node runs, including environments with no git installed.
  • Bare-repo native. Operates directly on git object databases and refs — no working directory, no index. A bare clone is a full-fledged store.
  • Mutable in-memory trees. Load a tree from any ref, upsert/delete/merge paths against it in memory, then flush only the dirty subtrees to the ODB and commit — the natural substrate for record stores, content pipelines, and anything that treats a git repo as a database.
  • Built for embedding. Stable machine-matchable error codes, panic containment at the FFI boundary (a Rust bug can never abort your process), and no reliance on thread-implicit state.

This package is a supported, standalone primitive — the Node-side consumption mode of the same crate that Rust consumers (e.g. gitsheets) link directly via Cargo. Both modes ship from holo-tree-v* tags and implement the same contract (specs/architecture.md § Embedding consumers).

Install

npm install @hologit/holo-tree

Prebuilt release binaries ship as optionalDependencies for: linux-x64-gnu, linux-arm64-gnu, linux-x64-musl, darwin-arm64, darwin-x64, win32-x64-msvc (full matrix under Publishing). No Rust toolchain needed on supported platforms.

Quick start

const { Repo, emptyTreeHash } = require('@hologit/holo-tree');

const repo = Repo.open('/path/to/repo.git'); // bare or .git dir

// load HEAD's tree, upsert a record, commit, advance the branch
const tree = repo.createTreeFromRef('HEAD'); // or repo.createTree() for empty
tree.writeChild('data/widgets/1.toml', 'id = 1\n'); // hash blob + deep insert
const treeHash = tree.write(); // flush dirty subtrees → ODB, returns tree hash

const parent = repo.resolveRef('HEAD');
const commit = repo.commitTree(treeHash, [parent], 'add widget 1');
repo.updateRef('refs/heads/main', commit, parent); // compare-and-swap

const bytes = repo.createTreeFromRef(commit).readBlob('data/widgets/1.toml');

Conventions: object ids cross the boundary as lowercase 40-char hex strings; blob content crosses as Buffer (binary-safe).

Errors: match on code, never on message

Every failure is a catchable Error carrying a stable code property — codes, not message prose, are the API (messages may change freely). The contract, including the full code table and stability rules, is specs/api/errors.md.

try {
  repo.updateRef('refs/heads/main', commit, expectedParent);
} catch (err) {
  switch (err.code) {
    case 'REF_CONFLICT': // someone else moved the ref — reload and retry
      return retryTransaction();
    case 'OBJECT_NOT_FOUND': // a hash you passed isn't in the ODB
      throw new StoreCorruptError(err.message);
    default:
      throw err;
  }
}

Common codes: REF_CONFLICT (lost a compare-and-swap updateRef — the optimistic-concurrency signal), OBJECT_NOT_FOUND, NOT_A_TREE, PATH_NOT_FOUND, INVALID_ARGUMENT (malformed hex id, bad merge mode, …), GIT (unclassified git failure), INTERNAL / PANIC (always a holo-tree bug — report it; the process stays healthy, but discard the involved objects).

Absence is a value, not an error: probes like readBlob, getChild, and resolveRef return null for "not there".

Performance — release builds only

⚠️ Never benchmark or ship a debug build. The published npm prebuilds are release builds — npm install users are fine. But if you build from source, napi build without --release produces a debug binary measured ~2.4× slower than the equivalent JS + git subprocess path on the reference workload, while the same code built with --release (npm run build) is ~4–5× faster. A debug build silently inverts the entire performance story. Check at runtime with buildProfile(), which returns 'release' or 'debug' — the bundled benchmark refuses debug builds.

Reference workload (real data: an 18k-record flat tree — see bench/): single-record upsert→commit ~25ms vs ~100ms for the JS hologit + git subprocess path; 500 records in one commit ~41ms vs ~186ms. Reproduce with:

npm run build && node bench/holo-tree-bench.mjs

Thread safety

Repo is backed by a gix::ThreadSafeRepository and is safe to hold anywhere. Each Tree owns its own tree cache, so behavior never depends on which thread the JS engine dispatches a call on — at worst a thread switch costs cache warmth, never correctness (specs/api/errors.md § Thread-safety expectations). Objects are not shareable across worker_threads (each worker must Repo.open its own handle); within one JS thread, calls are synchronous and complete before returning.

Surface

| Method | holo-tree call | Notes | | --- | --- | --- | | Repo.open(gitDir) | gix::open().into_sync() | factory; bare repo or .git dir | | repo.createTreeFromRef(ref)Tree | repo::create_tree_from_ref | resolves ref→commit→tree | | repo.createTree()Tree | MutableTree::empty | | | repo.commitTree(treeHash, parents[], msg, author?, committer?) → hash | repo::commit_tree | identities fall back to git config | | repo.updateRef(ref, hash, expectedOldHash?) | repo::update_ref | compare-and-swap when expectedOldHash given; force otherwise | | repo.resolveRef(ref)hash\|null | repo::resolve_ref | peels tags; null if unresolved | | repo.writeBlob(buf) → hash | gix write_blob | hash bytes into the ODB, no tree | | tree.writeChild(path, text) → hash | MutableTree::write_child | UTF-8 text | | tree.writeChildBytes(path, buf) → hash | MutableTree::write_child_bytes | binary | | tree.writeChildHash(path, hash, mode) → hash | MutableTree::write_child_hash | graft an existing ODB blob without re-hashing | | tree.readBlob(path)Buffer\|null | MutableTree::read_blob | | | tree.getChild(path){type,hash,mode}\|null | MutableTree::get_child | read-only; deep path | | tree.getChildren(path)[{name,type,hash,mode}] | get_subtree+ensure_children | read-only; direct children | | tree.getBlobMap(path?)[{path,hash,mode}] | get_subtree+get_blob_map | read-only; paths relative to subtree | | tree.deleteChildDeep(path) → bool | MutableTree::delete_child_deep | | | tree.clearChildren(path) | MutableTree::clear_children | O(1) subtree wipe | | tree.merge(other, {files?, mode}) | MutableTree::merge | mode: overlay/replace/underlay | | tree.write() → treeHash | MutableTree::write | flush dirty subtrees | | emptyTreeHash() → hash | tree::empty_tree_id | module fn | | buildProfile()'release'\|'debug' | — | guard against debug builds |

mode values are the git filemode as a number (e.g. 33188 = 0o100644). Tree hashes reported by the read-only navigators reflect the last write()/load and are stale for a subtree mutated since — flush with write() for canonical hashes.

The binding is a deliberately thin marshalling shell: rough edges are fixed upstream in the holo-tree crate, never papered over here (specs/principles.md). New capability requests belong against the crate.

Origins

This binding began as the integration vehicle for the gitsheets holo-tree spike — the first embedded consumer of the Rust crates. The Phase-C finding comments in the source record rough edges discovered during that hardening pass, each fixed upstream in holo-tree (error codes, panic containment, the consumer-owned tree cache). gitsheets has since moved to linking holo-tree directly as a Cargo crate; this package continues as the supported Node-side surface of the same crate.

Building

Requires a Rust toolchain and @napi-rs/cli (a devDependency):

npm install
npm run build         # release — use this for anything you'll measure or ship
npm run build:debug   # debug — compile-fast, runs SLOW (see Performance)
npm test              # node --test against a scratch git repo
npm run bench         # release-build benchmark (refuses debug builds)

napi build emits holo-tree.<triple>.node. The generated index.js loader and index.d.ts types are committed; only the .node binaries are git-ignored (built per-platform in CI).

Publishing

Published as the scoped package @hologit/holo-tree with per-platform prebuilt binaries shipped as optionalDependencies:

| Platform package | Triple | Built on | Smoke-tested | | --- | --- | --- | --- | | @hologit/holo-tree-linux-x64-gnu | x86_64-unknown-linux-gnu | ubuntu-latest | ✓ native | | @hologit/holo-tree-linux-arm64-gnu | aarch64-unknown-linux-gnu | ubuntu-24.04-arm | ✓ native | | @hologit/holo-tree-linux-x64-musl | x86_64-unknown-linux-musl | ubuntu-latest (musl cross) | build-only | | @hologit/holo-tree-darwin-arm64 | aarch64-apple-darwin | macos-latest | ✓ native | | @hologit/holo-tree-darwin-x64 | x86_64-apple-darwin | macos-latest (cross) | build-only | | @hologit/holo-tree-win32-x64-msvc | x86_64-pc-windows-msvc | windows-latest | ✓ native |

Native targets build + smoke-test on a matching runner; cross targets (musl, darwin-x64) build only, since their .node can't run on the host arch/libc (the logic is covered by the native runs). The .github/workflows/holo-tree-napi.yml workflow builds all six on every PR touching the binding, and on a holo-tree-v* tag it builds then publishes.

Auth is npm trusted publishing (OIDC) — no tokens, matching hologit's publish-npm.yml. Trusted publishing is configured per package, and a package can't get a trusted publisher until it exists — so the packages need a one-time manual bootstrap before automated releases work.

One-time bootstrap (manual first publish, then configure trusted publishing)

The packages all start at an early version (currently 0.0.1). They must exist on npm before trusted publishing can be turned on.

  1. Get the prebuilt binaries. Run the holo-tree-napi workflow (push the branch / open a PR, or trigger workflow_dispatch) and download its bindings-* artifacts — they hold the .node for each platform. A single machine can't build all of them natively, so use the CI artifacts.

  2. Publish all packages manually, logged in as an @hologit org member (npm login):

    cd holo-tree-napi
    npm install
    npx napi artifacts --dir <downloaded-artifacts-dir>   # → npm/<triple>/*.node
    # platform packages first, then the main package:
    for d in npm/*/ ; do ( cd "$d" && npm publish --access public ); done
    npm publish --access public --ignore-scripts          # main; skip the napi
                                                          # prepublish hook
  3. Turn on trusted publishing on npmjs.com for each of the packages → Settings → Trusted Publisher → GitHub Actions, repo JarvusInnovations/hologit, workflow holo-tree-napi.yml.

Releases (after bootstrap — fully automated, tokenless)

git tag holo-tree-v0.1.1 && git push origin holo-tree-v0.1.1

The tag drives the published version; CI builds all platforms, then publishes via OIDC (provenance). No secret needed. The holo-tree-v* tag is the release marker — napi runs with --skip-gh-release so it does not create a bare v<version> GitHub release/tag (which would collide with hologit's own v* JS-package release namespace). The same tag serves as the Cargo git-dependency pin for Rust consumers of the holo-tree crate.

To add or drop a platform later, edit napi.triples.additional + optionalDependencies in package.json, run napi create-npm-dir -t ., add the matching matrix entry in the workflow, and (since it's a new package) bootstrap

  • trust that one package too.