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

@lbstack/accessx

v0.3.1

Published

A role & resource based access control system with end to end type safety.

Downloads

46

Readme

@lbstack/accessx

A TypeScript-first RBAC permission engine with automatic permission generation, designed to be the single source of truth for:

  • Backend authorization
  • Frontend UI access control
  • Database permission storage
  • Admin permission management

No manual permission strings.
No role leakage to frontend.
Full autocomplete everywhere.


✨ Key Features

  • 🔐 Automatic permission generation
  • 🧠 Strong TypeScript inference & autocomplete
  • 🏗️ Single initialization – use everywhere
  • 🖥️ Backend (Express / Nest / Hono)
  • 🎨 Frontend (React hooks & components)
  • 🗄️ Database-friendly permission keys
  • 📦 Package & framework agnostic

🧩 Core Concept

You define:

  • Roles
  • Actions
  • Resources (modules)

The package automatically generates permissions in this format:

RESOURCE_KEY:ACTION

Example:

BLOGS:CREATE BLOGS:READ USER:DELETE

These permission keys are:

  • Stored in DB
  • Sent to frontend after login
  • Used in UI & API checks
  • Fully type-safe

📦 Installation

npm install @lbstack/accessx


or

pnpm add @lbstack/accessx

🚀 Initialization (Single Source of Truth)
import { createAccess } from "@lbstack/accessx";

export const access = createAccess({
  roles: ["ADMIN", "EDITOR", "CUSTOMER"] as const,

  actions: ["CREATE", "READ", "UPDATE", "DELETE"] as const,

  resources: [
    {
      name: "Users",
      key: "USER",
      description: "User management",
    },
    {
      name: "Blogs",
      key: "BLOGS",
      description: "Blog posts",
    },
  ] as const,
});


⚠️ as const is required for TypeScript autocomplete.

🔑 Automatically Generated Permissions
access.permissionKeys

[
  "USER:CREATE",
  "USER:READ",
  "USER:UPDATE",
  "USER:DELETE",
  "BLOGS:CREATE",
  "BLOGS:READ",
  "BLOGS:UPDATE",
  "BLOGS:DELETE",
]


No permission strings are written manually.

🗄️ Database Usage
Seed permissions table
await db.permissions.insertMany(access.permissions);


Each permission contains metadata:

{
  key: "BLOGS:CREATE",
  resource: { name, key, description },
  action: "CREATE"
}

🔐 Backend Usage
Assign permissions to roles
access.allow("ADMIN", "USER:DELETE");
access.allow("EDITOR", "BLOGS:CREATE");

Check permission (Service / Controller)
access.can(user.role, "BLOGS:UPDATE");

Normalize permissions from DB
const permissionsFromDb = ["BLOGS:READ", "USER:DELETE"];

const permissions = access.normalizePermissions(permissionsFromDb);


Ensures only valid generated permissions are used.

res.json({
  user,
  permissions: access.resolvePermissions(user.role),
});

🔑 Assigning Permissions

There are two ways to assign permissions to roles: Manual (Static) and Dynamic (Database-linked).

1. Manual Assignment (Static)

Best for hardcoded defaults or simple applications. This is the standard, non-mandatory approach.

// Single permission
access.allow("ADMIN", "USER:DELETE");

// Multiple permissions
access.allow("EDITOR", ["BLOGS:CREATE", "BLOGS:READ", "BLOGS:UPDATE"]);

// With custom ABAC conditions
access.allow("USER", "BLOG:UPDATE", (context) => {
  return context.post.authorId === context.user.id;
});

2. Dynamic Assignment (Database + Cache)

Best for production apps where permissions are managed in a DB or Admin Panel. This is optional but provides powerful caching and auto-sync benefits.

await access.assignPermissions("ADMIN", 
  // 1. Fetcher: Returns the list of valid permissions from your DB
  async () => {
    const permissions = await db.query("SELECT key FROM permissions WHERE role = 'ADMIN'");
    return permissions.map(p => p.key);
  }, 
  {
    // OPTIONAL: A fast key-check (e.g., Redis version or DB timestamp)
    // The engine only re-runs the Fetcher if this key changes.
    invalidateKey: async () => await redis.get("perms:admin:version"),
    
    // OPTIONAL: Auto-check for updates every 60 seconds in the background
    interval: 60000 
  }
);

[!TIP] Extra Benefits: By using invalidateKey, you avoid hitting your database for every permission check. The engine keeps permissions in an in-memory cache and only refetches when your "version" key in Redis/DB changes.

🔄 Manual Refresh & Sync

If you don't use the interval option, or if you need to force a sync after an admin update, use the refresh method.

// Forces the engine to check invalidateKeys and re-fetch if they changed
await access.refresh();

// Refresh only a specific role
await access.refresh("ADMIN");

🎨 Frontend Usage (React)

Frontend components are reactive. When you call access.refresh() or when an interval triggers an update, all components using the hooks will automatically re-render.

1. useCan Hook (Engine Bound)

Automatically re-renders when the engine's permissions for the given role change.

const canEdit = access.useCan("EDITOR", "BLOGS:UPDATE");

2. usePermissions Hook (Flexible Source)

Manage permissions from any source (Static, Async, or Role). Provides loading state and a manual refresh trigger.

const { permissions, loading, refresh } = access.usePermissions(async () => {
  const res = await api.get("/my-permissions");
  return res.data;
});

if (loading) return <Spinner />;

return (
  <div>
    <button onClick={() => refresh()}>Sync Permissions</button>
    <Can permissions={permissions} permission="BLOG:CREATE">
      <CreatePost />
    </Can>
  </div>
);

3. <Can /> Component

Works with both roles (engine-bound) and explicit permission arrays.

// Role-based (Reactive)
<access.Can role="ADMIN" permission="USER:DELETE">
  <DeleteButton />
</access.Can>

// Permission-based
<access.Can permissions={userPerms} permission="BLOGS:READ">
  <PostList />
</access.Can>

🧠 Type Safety & Autocomplete

Invalid permission → ❌ TypeScript error

Invalid resource/action → ❌ TypeScript error

IDE auto-suggests valid permissions everywhere

// ❌ Invalid "BLOGS:PUBLISH"

// ✅ Valid "BLOGS:CREATE"

🏗️ API Reference Metadata access.roles access.actions access.resources access.permissions access.permissionKeys

Backend access.allow(role, permission) access.assignPermissions(role, perms, options) // Async: Supports fetchers & invalidation access.can(role, permission) access.resolvePermissions(role) access.normalizePermissions(raw) access.refresh(role?) // Async: Trigger key check and conditional refetch

Frontend access.useCan(permissions, permission) <access.Can />

🔐 Multi-Permission Assignment Assign multiple permissions to a role at once:

access.allow("EDITOR", ["BLOGS:CREATE", "BLOGS:READ", "BLOGS:UPDATE"]);

🏆 Why Use @accessx/core?

Zero manual permission creation

DB, backend & frontend always in sync

Enterprise-grade RBAC foundation for apps

Scales to ABAC, multi-role, multi-tenant systems

🧠 ABAC (Attribute-Based Access Control)

You can define dynamic permissions based on context (e.g., user ID, ownership checking).

1. Define with Conditions

access.allow("USER", "BLOG:UPDATE", (context: { user: any; post: any }) => {
  return context.post.authorId === context.user.id;
});

2. Check with Context

The can method accepts an optional context object as the third argument.

const canEdit = access.can("USER", "BLOG:UPDATE", { user, post });

3. Frontend Usage

React components also support context.

<Can permissions={myPermissions} permission="BLOG:UPDATE" context={{ user, post }}>
  <button>Edit Post</button>
</Can>

🧪 Testing

The package includes a comprehensive test suite using Jest.

npm test

🛣️ Roadmap

  • [ ] Multi-role users
  • [ ] Permission groups
  • [ ] JWT permission compression
  • [ ] CLI generator

📄 License

MIT

💡 Inspiration

Zanzibar (Google)

Auth0 / Keycloak permission models

CASL & OPA (simplified DX)

One definition. One truth. Everywhere. 🔐✨