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

@bdcode/acl

v0.0.0-development

Published

A TypeScript-first authorization library with RBAC-first design, multi-tenant support, and optional ABAC policies

Readme

@bdcode/acl

A TypeScript-first authorization library with RBAC-first design, multi-tenant support, and optional ABAC policies. Built for modern applications with performance and developer experience in mind.

npm version Release License: MIT TypeScript

Features

RBAC-First Authorization

  • Role-Based Access Control with inheritance support
  • Wildcard permissions (inventory:*, *:read, *:*)
  • Direct user permissions for exceptions and overrides
  • Hierarchical roles with extends capability

🏗️ Multi-Tenant Ready

  • Single-tenant mode: Perfect for traditional applications
  • Multi-tenant mode: Ideal for SaaS applications with tenant isolation
  • Automatic tenant handling: Uses "default" tenant when none specified
  • Easy migration path from single-tenant to multi-tenant

🚀 Frontend + Backend

  • Server-side enforcement with database adapters
  • Client-side enforcement for UI permissions (Svelte, React, Vue)
  • Framework agnostic (Hono.js, Express, etc.)
  • Caching support for high-performance applications

High Performance

  • Adapter pattern for flexible data sources
  • Cache adapter support (Redis, in-memory)
  • Efficient role expansion with inheritance resolution
  • Minimized database queries through intelligent caching

🔒 Type Safe & Developer Friendly

  • Full TypeScript support with strict types
  • Permission format validation (resource:action)
  • Comprehensive error handling with meaningful messages
  • Detailed enforcement results with reasoning

Installation

npm install @bdcode/acl
# or
pnpm add @bdcode/acl
# or
yarn add @bdcode/acl

Quick Start

1. Basic Setup

import { ACL } from "@bdcode/acl";

// Create your data adapter (implement ACLAdapter interface)
const adapter = new MyDatabaseAdapter();

// Initialize ACL
const acl = new ACL({
  adapter,
  debug: true, // Enable debug logging
  wildcardSupport: true, // Enable wildcard permissions
});

2. Check Permissions

// Simple permission check
const allowed = await acl.can("user123", "posts", "read");

// With tenant (multi-tenant applications)
const allowed = await acl.can("user123", "posts", "read", "tenant-abc");

// Detailed enforcement result
const result = await acl.check({
  userId: "user123",
  resource: "posts",
  action: "delete",
  tenantId: "tenant-abc", // optional
});

console.log(result.allowed); // true/false
console.log(result.reason); // "RBAC: Allowed by permissions: posts:delete"
console.log(result.matchedRoles); // ["Editor", "Manager"]
console.log(result.matchedPermissions); // ["posts:delete"]

3. Frontend Client Enforcer

import { ACLClient } from "@bdcode/acl";

// Get ACL data from your API endpoint
const aclData = await fetch("/api/user/acl").then((r) => r.json());

// Create client enforcer (synchronous checks!)
const enforcer = new ACLClient(aclData);

// Check permissions in your UI
if (enforcer.can("posts", "delete")) {
  // Show delete button
}

if (enforcer.hasRole("Manager")) {
  // Show management features
}

// Check roles and permissions
console.log(enforcer.getRoles()); // ["Editor", "Manager"]
console.log(enforcer.getPermissions()); // ["posts:read", "posts:write", ...]

Core Concepts

Roles and Inheritance

// Roles can inherit from other roles
const viewerRole = {
  id: "viewer",
  name: "Viewer",
  permissions: [
    { resource: "posts", action: "read", effect: "allow" },
    { resource: "posts", action: "list", effect: "allow" },
  ],
};

const editorRole = {
  id: "editor",
  name: "Editor",
  extendsRoleId: "viewer", // Inherits all viewer permissions
  permissions: [
    { resource: "posts", action: "create", effect: "allow" },
    { resource: "posts", action: "update", effect: "allow" },
  ],
};

const adminRole = {
  id: "admin",
  name: "Administrator",
  extendsRoleId: "editor", // Inherits all editor + viewer permissions
  permissions: [
    { resource: "posts", action: "delete", effect: "allow" },
    { resource: "users", action: "*", effect: "allow" }, // Wildcard
  ],
};

Wildcard Permissions

// Wildcard examples
"posts:*"; // All actions on posts
"*:read"; // Read action on all resources
"*:*"; // All actions on all resources (super admin)

// The library automatically matches:
acl.can("user", "posts", "delete"); // matches "posts:*"
acl.can("user", "comments", "read"); // matches "*:read"

Multi-Tenant Support

// Single-tenant usage (automatic)
const canRead = await acl.can("user123", "posts", "read");

// Multi-tenant usage (explicit)
const canRead = await acl.can("user123", "posts", "read", "company-a");

// The library handles tenant isolation automatically

Data Adapter Interface

Implement the ACLAdapter interface to connect to your data source:

import { ACLAdapter } from "@bdcode/acl";

class DrizzleACLAdapter implements ACLAdapter {
  async getUser(userId: string, tenantId?: string) {
    // Return user with roles
    return {
      id: userId,
      tenantId,
      roles: ["editor", "viewer"],
    };
  }

  async getUserRoles(userId: string, tenantId?: string) {
    // Return expanded roles with permissions
    return roles;
  }

  async getRole(roleId: string, tenantId?: string) {
    // Return role with permissions
    return role;
  }

  async getRoleHierarchy(roleId: string, tenantId?: string) {
    // Return role inheritance chain
    return hierarchy;
  }

  async getUserDirectPermissions(userId: string, tenantId?: string) {
    // Return direct user permissions (overrides)
    return permissions;
  }

  async getTenant(tenantId: string) {
    // Optional: Return tenant information
    return tenant;
  }

  // ... other required methods
}

Cache Adapter (Optional)

Add caching for better performance:

import { CacheAdapter } from "@bdcode/acl";

class RedisACLCache implements CacheAdapter {
  async get<T>(key: string): Promise<T | null> {
    const value = await redis.get(key);
    return value ? JSON.parse(value) : null;
  }

  async set<T>(key: string, value: T, ttl?: number): Promise<void> {
    const serialized = JSON.stringify(value);
    if (ttl) {
      await redis.setex(key, ttl, serialized);
    } else {
      await redis.set(key, serialized);
    }
  }

  async del(key: string): Promise<void> {
    await redis.del(key);
  }
}

// Use with ACL
const acl = new ACL({
  adapter: new DrizzleACLAdapter(),
  cache: new RedisACLCache(),
  cacheTTL: 300, // 5 minutes
});

Configuration Options

interface ACLConfig {
  adapter: ACLAdapter; // Required: Your data adapter
  cache?: CacheAdapter; // Optional: Caching layer
  cacheTTL?: number; // Cache TTL in seconds (default: 300)
  enablePolicies?: boolean; // Enable ABAC policies (future)
  wildcardSupport?: boolean; // Enable wildcard permissions (default: true)
  debug?: boolean; // Enable debug logging (default: false)
  defaultTenantId?: string; // Default tenant ID (default: "default")
}

Framework Integration Examples

Hono.js Middleware

import { authorize } from "./middleware/acl";

app.use("/posts/*", authMiddleware); // Ensure user is authenticated
app.get("/posts", authorize("posts", "list"), async (c) => {
  // User is authorized to list posts
  return c.json({ posts: [] });
});

Express.js Middleware

import { ACL } from "@bdcode/acl";

const acl = new ACL({ adapter: new MyAdapter() });

const authorize = (resource: string, action: string) => {
  return async (req, res, next) => {
    const allowed = await acl.can(
      req.user.id,
      resource,
      action,
      req.user.tenantId,
    );
    if (!allowed) {
      return res.status(403).json({ error: "Forbidden" });
    }
    next();
  };
};

app.get("/posts", authorize("posts", "list"), (req, res) => {
  res.json({ posts: [] });
});

React Hook Example

import { useACL } from "./hooks/useACL";

function PostList() {
  const { can, hasRole } = useACL();

  return (
    <div>
      {can("posts", "create") && (
        <button>Create Post</button>
      )}

      {hasRole("Manager") && (
        <AdminPanel />
      )}
    </div>
  );
}

Sveltekit (as client app)

// src/routes/+page.svelte

<script lang="ts">
  import { onMount } from "svelte";
  import { ACLClient } from "@bdcode/acl";

  let enforcer: ACLClient;

  onMount(async () => {
    const aclData = await fetch("/api/user/acl").then((r) => r.json());
    enforcer = new ACLClient(aclData);
  });
</script>

{#if enforcer?.can("posts", "create")}
  <button>Create Post</button>
{/if}

{#if enforcer?.hasRole("Manager")}
  <AdminPanel />
{/if}

API Reference

ACL Class

  • can(userId, resource, action, tenantId?) - Quick permission check
  • check(context) - Detailed enforcement with reasoning
  • getUserACLData(userId, tenantId?) - Get user data for client enforcer
  • clearUserCache(userId, tenantId?) - Clear cached user data

ACLClient Class

  • can(resource, action) - Check permission (synchronous)
  • cannot(resource, action) - Inverse permission check
  • hasRole(role) - Check if user has role
  • getRoles() - Get all user roles
  • getPermissions() - Get all user permissions
  • getTenantId() - Get current tenant ID

Error Handling

The library provides specific error types for different scenarios:

import {
  ACLError,
  UserNotFoundError,
  TenantNotFoundError,
  UnauthorizedError,
  RoleNotFoundError,
} from "@bdcode/acl";

try {
  await acl.check({ userId: "user123", resource: "posts", action: "delete" });
} catch (error) {
  if (error instanceof UserNotFoundError) {
    console.error("User not found:", error.message);
  } else if (error instanceof UnauthorizedError) {
    console.error("Access denied:", error.message);
  }
}

Performance Tips

  1. Use caching: Implement a cache adapter for frequently accessed permissions
  2. Preload user data: Call getUserACLData() once and use ACLClient for multiple checks
  3. Optimize queries: Implement efficient database queries in your adapter
  4. Batch operations: Group permission checks when possible

Debugging

Enable debug mode to see detailed logging:

const acl = new ACL({
  adapter: myAdapter,
  debug: true, // Enable debug logging
});

// Output example:
// [ACL] Enforcement result for user123:posts:delete = true
// [ACL] Cache hit for acl:user123:default:posts:delete
// [ACL] Matched roles: ["Editor", "Manager"]

Migration Guide

From v0.x to v1.x

The library is currently in development. Breaking changes will be documented here as we approach v1.0.

TypeScript Support

This library is written in TypeScript and provides full type safety:

import type {
  ACLConfig,
  EnforcementContext,
  EnforcementResult,
  Permission,
  Role,
  User,
} from "@bdcode/acl";

// All types are fully typed and provide excellent IntelliSense

Contributing

This library is part of a larger authorization framework. See the main repository for contribution guidelines.

License

MIT © CodeContinent


@bdcode/acl - Authorization that scales with your application 🚀