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

@stonedogcode/rbac

v0.1.0

Published

A shared authorisation model: capabilities, checked against a scope. No role catalogue, no ordering, no framework.

Readme

@stonedogcode/rbac

A shared authorisation model: capabilities, checked against a scope.

The model was agreed before any code was written, because it is very hard to change once applications have adopted it. See docs/prd/authorisation-model.md for the reasoning and for the three decisions that shaped it.

The shape

can(subject, capability, scope?) : boolean

Call sites ask for the permission they need, never for the role that happens to carry it:

can(subject, "article:read", { organisation: orgId });   // yes
hasPermission(role, 99);                                  // no
roleName === "Some Admin";                                // no

Three properties the design is built around, each of which an ordered numeric role ladder cannot provide:

  • Scope. A subject holds capabilities within a scope, and a grant in one scope never satisfies a check in another. An application with a single global scope omits the argument.
  • Lateral roles. A role that grants a different area rather than a higher one has no position on a >= scale. Under a ladder these end up as string comparisons against role names; here they are simply a different capability set.
  • Rename resistance. Renaming, splitting, or adding a role changes one mapping rather than every call site that named it.

What it will not hold

No role catalogue. Role names are product data and stay in the application that owns them. This package ships the evaluator and the types; each host supplies its own role → capability mapping from wherever its roles already live.

No authentication, no policy language, no row-level filtering. See the non-goals in the PRD. Authentication is @stonedogcode/auth, and it is a separate package for the same reason this one has no role catalogue.

No negation. Grant-only, deliberately. Negation makes the answer depend on the order grants were applied and makes "why was this denied" unanswerable without a trace. The cases that look like denial — a suspended account, a read-only member — are better modelled as a smaller capability set. It can be added later; it cannot be removed later.

Usage

import { can, subjectFromRoles } from "@stonedogcode/rbac";

const roleMap = {
  Viewer: ["article:read"],
  Editor: ["article:read", "article:write"],
  "Artwork Admin": ["artwork:manage"],   // lateral, not higher
};

const subject = subjectFromRoles(
  [{ role: "Editor", scope: orgId }, { role: "Artwork Admin" }],
  roleMap,
);

can(subject, "article:write", orgId);      // true
can(subject, "article:write", otherOrgId); // false — a grant does not travel
can(subject, "article:write");             // false — nor does it become global

Scope hierarchy

Scope is opaque: the package compares scopes and does nothing else with them. If yours nest, supply a resolver that answers what contains this:

can(subject, "article:read", teamId, {
  resolveContainingScopes: (scope) => parentsOf(scope),
});

The direction is load-bearing. A resolver that returns a scope's children inverts every check — a grant on one team would satisfy a check on its organisation, and so on every other team in it. Nothing throws and nothing looks wrong. The type is named containingScopes for that reason, and the walk's direction is pinned by a test against a deliberately inverted resolver.

A cyclic hierarchy is a host bug; the walk is depth-bounded and denies rather than hanging. An authorisation check is the wrong place to turn a configuration mistake into an outage.

The ladder adapter

ladderRoleMap turns an ordered tier list into a role map, so an application with a numeric ladder can adopt this without rewriting every call site at once.

It ships deprecated. It exists to make migration incremental and for no other reason, and it is removed in the first major release after the last ladder-shaped call site is gone. Treat every call site it supports as work remaining rather than work done — a ladder cannot express a lateral role or a scoped one, which is what sends those call sites back to comparing role names as strings.

What it must never become

A prerequisite. Anything needing an authorisation answer should be able to accept a callback and get on with it — this package supplies a ready-made implementation of such a callback as a convenience. If it becomes required, the interface has been drawn wrong.

Licence

Apache-2.0. See LICENSE and NOTICE.