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

@willyim/rbac

v0.2.1

Published

Typed RBAC where permissions are the primitive and roles are named permission bags

Readme

@willyim/rbac

Typed RBAC where permissions are the primitive and roles are named permission bags.

Define your permissions once, get fully typed checkers for your server loaders and a hook factory for your React components.

Install

npm install @willyim/rbac

Define permissions

import { definePermissions } from "@willyim/rbac"

export const auth = definePermissions({
  permissions: [
    "contacts:read",
    "contacts:import",
    "reports:read",
    "calendar:read",
    "calendar:read-all",
    "settings:read",
    "settings:manage",
  ] as const,
  roles: {
    admin: [
      "contacts:read",
      "contacts:import",
      "reports:read",
      "calendar:read",
      "calendar:read-all",
      "settings:read",
      "settings:manage",
    ],
    agent: ["contacts:read", "calendar:read", "settings:read"],
  },
})

// Derive types from your config
export type Permission = (typeof auth.permissions)[number]
export type Role = keyof typeof auth.roles

Permissions and roles are inferred from your config — typos in role arrays are caught at compile time.

Check permissions (server)

createChecker returns a PermissionChecker scoped to a role. Use it in loaders, middleware, or anywhere on the server.

const checker = auth.createChecker("agent")

checker.has("contacts:read") // true
checker.has("reports:read") // false

// Throws a 403 Response if the permission is not granted
checker.require("reports:read") // throws Response("Forbidden", { status: 403 })

Superadmin

Pass { superadmin: true } to grant all permissions regardless of role. The library handles the semantics; your app decides who is a superadmin.

const checker = auth.createChecker("agent", { superadmin: isSuperAdmin(user.email) })

checker.has("reports:read")   // true — superadmin bypasses role restrictions
checker.isSuperadmin          // true
checker.granted               // all permissions in the system

Add superadmin-only features as permissions that no role lists. Only superadmins (via the flag) will ever have them:

// permissions.ts
export const auth = definePermissions({
  permissions: ["contacts:read", /* ... */, "jobs:manage"] as const,
  roles: {
    admin: ["contacts:read", /* ... */],  // jobs:manage not listed — only superadmins get it
  },
})

// route loader
if (!permissions.has("jobs:manage")) throw new Response("Not Found", { status: 404 })

React Router loader example

// app/lib/session.ts
import { auth } from "./permissions"

export async function getSessionContext() {
  const role = await getRoleFromSession()
  const permissions = auth.createChecker(role, { superadmin: isSuperAdmin(user.email) })
  return { permissions, /* ... */ }
}

// app/routes/reports.tsx
export async function loader({ context }: Route.LoaderArgs) {
  const { permissions } = await context.getSessionContext()
  permissions.require("reports:read")
  return { /* ... */ }
}

Check permissions (React)

The @willyim/rbac/react entry point exports createPermissionsHook, a factory that builds a typed usePermissions hook from any data source.

// app/hooks/use-permissions.ts
import { createPermissionsHook } from "@willyim/rbac/react"
import { useRouteLoaderData } from "react-router"
import type { Permission } from "../lib/permissions"

export const usePermissions = createPermissionsHook<Permission>(() => {
  const data = useRouteLoaderData("routes/_dashboard_layout")
  return {
    granted: data?.permissions.granted ?? [],
    isSuperadmin: data?.permissions.isSuperadmin ?? false,
  }
})

Then use it in components:

function Nav() {
  const { has } = usePermissions()

  return (
    <nav>
      <Link to="/">Home</Link>
      {has("reports:read") && <Link to="/reports">Reports</Link>}
      {has("settings:manage") && <Link to="/settings">Settings</Link>}
    </nav>
  )
}

API

definePermissions(config)

| Parameter | Type | Description | |-----------|------|-------------| | config.permissions | readonly string[] | All permissions in the system (use as const) | | config.roles | Record<string, readonly Permission[]> | Each role maps to a subset of permissions |

Returns an object with:

  • createChecker(role, opts?) — returns a PermissionChecker for the given role
  • permissions — the original permissions array
  • roles — the original roles config

PermissionChecker<P>

| Member | Description | |--------|-------------| | has(permission) | Returns true if the permission is granted (always true for superadmin) | | require(permission) | Throws Response("Forbidden", { status: 403 }) if not granted (never throws for superadmin) | | granted | Array of all granted permissions (all permissions for superadmin) | | isSuperadmin | true when the checker was created with { superadmin: true } |

CheckerOptions

| Option | Type | Default | Description | |--------|------|---------|-------------| | superadmin | boolean | false | When true, all has() checks pass and granted returns every permission |

createPermissionsHook(useData)

| Parameter | Type | Description | |-----------|------|-------------| | useData | () => { granted: P[], isSuperadmin?: boolean } | A React hook that returns permissions data from your loader |

Returns a usePermissions hook with:

| Property | Description | |----------|-------------| | has(permission) | Returns true if the permission is granted | | granted | Array of all granted permissions | | isSuperadmin | true when the data source explicitly sets it (not inferred) |

License

MIT