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

pr-lens-git-backend

v0.7.0

Published

A lightweight PR Lens canvas server: version 1 of the canvas API, with a git repository as the store.

Readme

🗺️ pr-lens-git-backend

A private PR Lens canvas server — with a git repository as the whole database.

ci npm image helm canvas API license

It speaks version 1 of the canvas API in full, so pr-lens canvas push, pull, rotate and delete work against it exactly as against prlens.dev — and a document pushed here never touches prlens.dev.

export PR_LENS_API_URL=https://lens.example.com
pr-lens canvas push

It draws, too. The renderer is MIT, so pushes come back with the same tiles the hosted app would answer with — this is a real one, straight out of renderAll:

🧩 Why a git repository is enough

The contract needs one thing that is easy to get wrong: If-Match has to be a real compare-and-swap, or two pushes on the same revision silently overwrite each other.

Git hands that over twice. A blob's object id is a hash of its contents, so a read can hand back "the version I gave you" for free and a write can insist the path still holds it. And a push is a fast-forward or it is refused, so no write can land on a tip its author never saw:

sequenceDiagram
    autonumber
    participant C as pr-lens CLI
    participant S as this server
    participant G as the remote

    C->>S: PUT /api/canvas/{id} · If-Match 3
    S->>G: fetch
    G-->>S: tip abc123 · blob 9f8e…
    S->>G: push rev 4, fast-forward from abc123
    alt abc123 is still the tip
        G-->>S: taken
        S-->>C: 200 · rev 4 · tiles
    else somebody pushed first
        S->>G: fetch, and look at the blob again
        alt still 9f8e… — it was a different canvas
            S->>G: push the commit, rebuilt
            S-->>C: 200 · rev 4 · tiles
        else the blob moved
            S-->>C: 409 REVISION_MOVED · rev 4
        end
    end

No database, no lock, no lease, and no host-specific API — gitlab.com, a self-managed GitLab, GitHub, Gitea, Forgejo, a bare repository over ssh. Nothing is ever checked out: the server keeps a bare mirror and writes through plumbing, and that mirror is a cache it can lose without losing a canvas.

And a bonus the contract explicitly does not offer: every push is a commit, so git log over a canvas file is the revision history.

This began as a GitLab-API server, and the repository it wrote is the one this reads — same paths, same bytes, same commit messages, so moving is renaming a few environment variables. The long version has the reasoning and the costs; the migration table has the renames.

🚀 Start here

STORE=memory npx pr-lens-git-backend   # forgets everything on restart

Then the tutorial — a canvas of your own, in a repository of your own, in about ten minutes.

📚 Documentation

| | | | --- | --- | | 🎓 Tutorial | Your first canvas, from nothing. | | 🔧 How-to | Connect a remote · Deploy with Helm · Put a CDN in front · Cut a release | | 📖 Reference | Configuration · Routes · Storage layout | | 💡 Explanation | Why git works · Determinism and caching |

⚡ The CDN half

Pictures are never stored. The renderer reads no clock, no file and no random number, so the same document always produces the same bytes — which means any replica can redraw any picture it holds the document for, and a picture's hash can be its name:

| Route | Cache-Control | | | --- | --- | --- | | /images/{id}/…-{hash}.svg | immutable, one year | ♾️ the hash is in the name, so these bytes can never change | | /c/{id}.svg | 60 s + ETag | 🔄 the embed address stays put, so it revalidates on the render's hash | | /c/{id} · /api/canvas/… | no-store | 🔒 the contract's own rule: no cache may keep a document under a secret address |

Put a CDN in front and the only traffic reaching this server is the API. No CDN to reach for? cache.enabled=true puts Vinyl Cache in the pod as a sidecar and points the Service at it — and it needs no path rules, because the headers above are already the whole policy. How, and the one thing to get right.

🔐 Security in one list

  • 🎲 Ids and tokens are 128 random bits, base64url, as the contract specifies. The id is the read capability, the token the write capability.
  • 🧂 Only a hash of the token is stored, compared in constant time. A copy of the repository is not a copy of every token — there is a test for exactly that.
  • 🕳️ NOT_FOUND covers an unminted id, a wrong token and an unpushed canvas with one answer, so a guesser learns nothing.
  • 📓 Ids stay out of the logs. An access log should not be a list of live canvases.
  • 🔗 The write token rides in a URL fragment and nowhere else, so no browser sends it to a server or a referrer.
  • 🤐 An internal fault leaves as a bare 500 with no envelope, which the CLI reads as "unavailable" — the truth, rather than a made-up code it would act on.

🛠️ Development

npm ci
npm test          # 61: the contract over HTTP, the store against a real repository
npm run typecheck
npm run dev       # reloads on change
npm run build     # dist/, which is what gets published

TypeScript run directly by Node — no bundler, no loader, no build step to develop against. Node 22.18 or newer strips the types itself; the published package and the container image run the compiled dist/.

Releasing is merging a pull request. release-please keeps one open, reading the conventional-commit subjects to pick the next version and write the changelog; merging it tags, and the tag publishes all three artifacts at that version — npm with provenance, ghcr.io/thejoeejoee/pr-lens-git-backend for amd64 and arm64, and oci://ghcr.io/thejoeejoee/charts/pr-lens-git-backend. There are no publishing secrets: npm is reached over OIDC as a trusted publisher, ghcr with the built-in token. Cut a release has the detail.

📄 License

MIT, as are the renderer and schema packages it builds on.