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

@faryzal2020/v-perms

v1.0.2

Published

Minimal, flexible role-based permission system for JavaScript/Bun.js applications with Prisma

Readme

@faryzal2020/v-perms

Minimal, flexible role-based permission system for JavaScript/Bun.js applications with Prisma.

Features

  • Role-based access control (RBAC) - Assign roles to users, permissions to roles
  • Role inheritance - Roles can inherit permissions from other roles
  • User-specific overrides - Direct user permissions take priority over role permissions
  • Wildcard permissions - Grant access to multiple permissions at once (endpoint.*, *)
  • Ban/deny permissions - Explicitly deny access even when wildcards would allow
  • Redis caching - Optional distributed caching for high performance
  • Database agnostic - Compatible with PostgreSQL, MySQL, SQLite via Prisma
  • Runtime flexible - Works with both Bun.js and Node.js
  • Zero dependencies - Only requires Prisma (peer dependency)

Documentation

Installation

npm install @faryzal2020/v-perms
# or
bun add @faryzal2020/v-perms

Quick Start

1. Add Schema to Your Prisma Schema

Copy the models from src/prisma/schema.prisma into your existing Prisma schema file. See examples/schema-usage.md for detailed instructions.

2. Run Migrations

npx prisma migrate dev --name add-permissions

3. Initialize in Your Code

import { PrismaClient } from '@prisma/client';
import { createPermissionSystem } from '@faryzal2020/v-perms';

const prisma = new PrismaClient();
const perms = createPermissionSystem(prisma, {
  debug: true, // Enable debug logging
  enableCache: true,
  cacheTTL: 300, // 5 minutes
  redis: redisClient, // optional
});

4. Create Permissions and Roles

// Create permissions
await perms.createPermission('page.admin', 'Access admin pages', 'page');
await perms.createPermission('endpoint.users.list', 'List users', 'endpoint');
await perms.createPermission('endpoint.users.delete', 'Delete users', 'endpoint');

// Create roles
await perms.createRole('member', 'Basic user role', 1, true); // isDefault=true
await perms.createRole('admin', 'Administrator', 10);

// Assign permissions to roles
await perms.assignPermission('page.admin', 'admin', 'role');
await perms.assignPermission('endpoint.*', 'admin', 'role'); // Wildcard

// Set role inheritance (admin inherits member permissions)
const memberRole = await perms.manager.getRole('member');
const adminRole = await perms.manager.getRole('admin');
await perms.manager.setRoleInheritance(adminRole.id, memberRole.id);

5. Assign Roles to Users

const adminRole = await perms.manager.getRole('admin');
await perms.assignRole(adminRole.id, userId);

6. Check Permissions

// Check user permission
const canAccess = await perms.can(userId, 'page.admin');

// Check role permission
const roleCanAccess = await perms.canRole(roleId, 'endpoint.users.delete');

API Reference

Factory Function

createPermissionSystem(prismaClient, options)

Creates a permission system instance.

Parameters:

  • prismaClient (PrismaClient): Prisma client instance
  • options (object):
    • redis (RedisClient): Redis client for caching (optional)
    • enableCache (boolean): Enable caching (default: true)
    • cacheTTL (number): Cache TTL in seconds (default: 300)
    • debug (boolean): Enable debug logging (default: false)

Returns: Permission system instance with methods

Permission Operations

createPermission(key, description?, category?)

Create a new permission.

await perms.createPermission('endpoint.posts.create', 'Create posts', 'endpoint');

deletePermission(permissionKey)

Delete a permission.

await perms.deletePermission('endpoint.posts.create');

manager.listPermissions()

List all permissions.

const permissions = await perms.manager.listPermissions();

Role Operations

createRole(name, description?, priority?, isDefault?)

Create a new role.

await perms.createRole('moderator', 'Moderator role', 5, false);

Parameters:

  • name (string): Unique role name
  • description (string): Role description
  • priority (number): Role priority (higher = more important, default: 0)
  • isDefault (boolean): Auto-assign to new users (default: false)

deleteRole(roleIdOrName)

Delete a role.

await perms.manager.deleteRole('moderator');

manager.getRole(roleIdOrName)

Get role by ID or name.

const role = await perms.manager.getRole('admin');

manager.listRoles()

List all roles.

const roles = await perms.manager.listRoles();

Assignment Operations

assignPermission(permissionKey, targetId, targetType)

Assign permission to role or user.

// Assign to role
await perms.assignPermission('page.admin', roleId, 'role');

// Assign to user (user-specific override)
await perms.assignPermission('feature.export', userId, 'user');

// Wildcard assignment
await perms.assignPermission('endpoint.*', roleId, 'role');

banPermission(permissionKey, targetId, targetType)

Explicitly deny a permission (sets granted=false).

// Ban admin page access for specific user
await perms.banPermission('page.admin', userId, 'user');

// Admin has endpoint.* but ban delete specifically
await perms.assignPermission('endpoint.*', adminRoleId, 'role');
await perms.banPermission('endpoint.users.delete', adminRoleId, 'role');

assignRole(roleIdOrName, userId)

Assign role to user.

await perms.assignRole('admin', userId);

manager.removeRole(roleIdOrName, userId)

Remove role from user.

await perms.manager.removeRole('admin', userId);

manager.setRoleInheritance(roleId, inheritFromRoleId, priority?)

Set role inheritance.

// Admin inherits all member permissions
await perms.manager.setRoleInheritance(adminRole.id, memberRole.id, 1);

Check Operations

can(userId, permissionKey)

Check if user has permission. This is the main method you'll use.

const hasAccess = await perms.can(userId, 'endpoint.users.delete');

if (hasAccess) {
  // Allow action
} else {
  // Deny action
}

Resolution Order:

  1. User-specific permissions (highest priority)
  2. User-specific wildcard permissions
  3. Direct role permissions (by role priority)
  4. Role wildcard permissions
  5. Inherited role permissions
  6. Inherited role wildcard permissions
  7. Default deny

canRole(roleId, permissionKey)

Check if role has permission.

const roleHasAccess = await perms.canRole(roleId, 'page.admin');

Cache Operations

invalidateUserCache(userId)

Clear cache for specific user.

await perms.invalidateUserCache(userId);

invalidateRoleCache(roleId)

Clear cache for specific role.

await perms.invalidateRoleCache(roleId);

clearCache()

Clear entire permission cache.

await perms.clearCache();

Wildcard Permissions

Wildcard permissions allow you to grant access to multiple permissions at once.

// Grant access to all endpoint permissions
await perms.assignPermission('endpoint.*', roleId, 'role');

// Now user has access to:
// - endpoint.users.list
// - endpoint.users.create
// - endpoint.posts.delete
// etc.

// Grant all permissions (god mode)
await perms.assignPermission('*', superadminRoleId, 'role');

Wildcard Matching Rules

  • * matches everything
  • endpoint.* matches endpoint.users, endpoint.posts.create, etc.
  • endpoint.users.* matches endpoint.users.list, endpoint.users.delete, etc.
  • Wildcards don't match partial segments: endpoint.* does NOT match endpoint itself

Ban/Deny Permissions

You can explicitly deny permissions, which is useful for exceptions.

// Admin has all endpoint access
await perms.assignPermission('endpoint.*', adminRoleId, 'role');

// But ban delete operations
await perms.banPermission('endpoint.users.delete', adminRoleId, 'role');
await perms.banPermission('endpoint.posts.delete', adminRoleId, 'role');

// User-specific ban (override role permissions)
await perms.banPermission('endpoint.sensitive', userId, 'user');

Priority: Deny (granted=false) takes precedence at the point it's checked (user level, then role level).

Role Inheritance

Roles can inherit permissions from other roles.

// Create role hierarchy
await perms.createRole('member', 'Basic user', 1);
await perms.createRole('moderator', 'Moderator', 5);
await perms.createRole('admin', 'Administrator', 10);

// Assign permissions
await perms.assignPermission('page.home', 'member', 'role');
await perms.assignPermission('page.profile', 'member', 'role');

// Moderator inherits member permissions + has more
await perms.manager.setRoleInheritance('moderator', 'member');
await perms.assignPermission('page.moderation', 'moderator', 'role');

// Admin inherits moderator (which inherits member)
await perms.manager.setRoleInheritance('admin', 'moderator');
await perms.assignPermission('page.admin', 'admin', 'role');

Result: Admin has permissions from member + moderator + admin

Circular Inheritance: The system prevents circular inheritance and will throw CircularInheritanceError.

Redis Caching

For production environments with multiple instances, use Redis for distributed caching.

import { createClient } from 'redis';

const redisClient = createClient({
  url: 'redis://localhost:6379'
});
await redisClient.connect();

const perms = createPermissionSystem(prisma, {
  redis: redisClient,
  enableCache: true,
  cacheTTL: 300, // 5 minutes
});

Cache Keys Format:

  • User permissions: v-perms:user:{userId}:{permissionKey}
  • Role permissions: v-perms:role:{roleId}:{permissionKey}

When to Invalidate Cache:

  • After assigning/removing permissions: invalidateUserCache(userId) or invalidateRoleCache(roleId)
  • After role changes: invalidateRoleCache(roleId)
  • After role inheritance changes: invalidate both roles

Error Handling

The package throws descriptive errors instead of returning false.

import { errors } from '@faryzal2020/v-perms';

try {
  await perms.assignRole('nonexistent-role', userId);
} catch (error) {
  if (error instanceof errors.RoleNotFoundError) {
    console.error('Role not found:', error.details.roleId);
  }
}

Error Types:

  • UserNotFoundError
  • RoleNotFoundError
  • PermissionNotFoundError
  • RoleAlreadyAssignedError
  • PermissionAlreadyExistsError
  • RoleAlreadyExistsError
  • CircularInheritanceError

Debug Logging

Enable debug logging to see what's happening under the hood.

const perms = createPermissionSystem(prisma, {
  debug: true,
});

// Or enable later
perms.logger.enable();

// Disable
perms.logger.disable();

Log Output:

[v-perms:debug] checkPermission: user-123 endpoint.users.list
[v-perms:debug] Cache miss
[v-perms:debug] User roles (with inheritance): ['admin', 'member']
[v-perms:debug] Role wildcard match: admin endpoint.* true

Common Patterns

Middleware (Express Example)

function requirePermission(permission) {
  return async (req, res, next) => {
    const userId = req.user?.id;
    
    if (!userId) {
      return res.status(401).json({ error: 'Unauthorized' });
    }

    try {
      const hasPermission = await perms.can(userId, permission);
      
      if (!hasPermission) {
        return res.status(403).json({ error: 'Forbidden' });
      }
      
      next();
    } catch (error) {
      console.error('Permission check error:', error);
      res.status(500).json({ error: 'Internal server error' });
    }
  };
}

// Usage
app.get('/api/users', 
  authenticate, 
  requirePermission('endpoint.users.list'),
  async (req, res) => {
    // Handler
  }
);

Assign Default Role on User Registration

async function registerUser(userData) {
  const user = await prisma.user.create({
    data: userData,
  });

  // Get default role
  const defaultRole = await prisma.role.findFirst({
    where: { isDefault: true },
  });

  if (defaultRole) {
    await perms.assignRole(defaultRole.id, user.id);
  }

  return user;
}

Seed Initial Permissions

async function seedPermissions() {
  const permissions = [
    { key: 'page.home', desc: 'Home page', cat: 'page' },
    { key: 'page.admin', desc: 'Admin dashboard', cat: 'page' },
    { key: 'endpoint.users.list', desc: 'List users', cat: 'endpoint' },
    { key: 'endpoint.users.create', desc: 'Create user', cat: 'endpoint' },
    { key: 'endpoint.users.delete', desc: 'Delete user', cat: 'endpoint' },
  ];

  for (const perm of permissions) {
    try {
      await perms.createPermission(perm.key, perm.desc, perm.cat);
    } catch (error) {
      // Permission already exists, skip
    }
  }
}

License

MIT

Contributing

Contributions welcome! Please open an issue or PR.

Support

For issues and questions, please use GitHub Issues.