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

@fimbul-works/acl

v1.0.0

Published

Functional Hierarchical Access Control Layer (ACL)

Readme

@fimbul-works/acl

A functional, type-safe Access Control Layer (ACL) library for TypeScript/JavaScript applications.

npm version TypeScript

Features

  • 🔒 Fine-grained permissions with hierarchical pattern matching
  • 🌟 Wildcard support - Use * to match any segment or resource
  • High performance - Optimized permission checking with automatic sorting
  • 🔀 Functional API - Immutable operations, predictable behavior
  • 📝 Type-safe - Full TypeScript support with comprehensive types
  • 💾 Persistence ready - Serialize/deserialize for databases and JWTs
  • 🛠️ Developer tools - Debugging, validation, and permission math utilities
  • 🎯 Zero dependencies - Lightweight and easy to adopt

Installation

npm install @fimbul-works/acl
# or
yarn add @fimbul-works/acl
# or
pnpm install @fimbul-works/acl

Quick Start

import {
  createPermissions,
  checkPermission,
  serializePermissions,
  deserializePermissions
} from "@fimbul-works/acl";

// Define permissions
const permissions = createPermissions([
  ["allow", "discussion.*.read"], // Allow reading any discussion
  ["allow", "user.1234.*"],       // Allow user 1234 to do anything
  ["deny", "admin.*"],            // Deny all admin access
  ["allow", "*.public.view"],     // Allow viewing public resources
]);

// Check permissions
checkPermission(permissions, "discussion.abc.read"); // true
checkPermission(permissions, "admin.panel");         // false
checkPermission(permissions, "user.1234.edit");      // true

// Serialize for storage (databases, JWTs, etc.)
const serialized = serializePermissions(permissions);
// "deny:admin.*,allow:*.public.view,allow:discussion.*.read,allow:user.1234.*"

// Deserialize back to permissions
const restored = deserializePermissions(serialized);

Pattern Syntax

Permissions use dot-separated patterns with wildcards:

  • * - Root access (matches everything)
  • user.* - Matches any resource under user namespace (e.g., user.read, user.profile.edit)
  • *.public - Matches resources ending with .public (e.g., user.public, admin.public)
  • user.*.read - Matches user read operations (e.g., user.1234.read, user.profile.read)
  • discussion.1234.moderate - Exact match only

Precedence Rules

Permissions are evaluated in this order:

  1. Deny overrides Allow - A deny always wins over an allow
  2. Root access - * patterns have special handling
  3. Specific over General - More specific patterns take precedence
const permissions = createPermissions([
  ["allow", "*"],      // Allow everything
  ["deny", "admin.*"], // But deny admin
]);

checkPermission(permissions, "user.read");   // true (root allow)
checkPermission(permissions, "admin.panel"); // false (deny wins)

Core API

Creating Permissions

import {
  createPermission,
  createPermissions,
  addPermission,
  removePermission
} from "@fimbul-works/acl";

// Create single permission
const perm = createPermission("allow", "user.*.read");

// Create multiple permissions
const permissions = createPermissions([
  ["allow", "discussion.*.read"],
  ["deny", "admin.*"],
]);

// Add permission (returns new array)
const updated = addPermission(permissions, "allow", "user.*.write");

// Remove permission
const filtered = removePermission(permissions, "deny", "admin.*");

Checking Permissions

import {
  checkPermission,
  checkPermissions,
  getMatchingPermissions,
  hasRootAccess
} from "@fimbul-works/acl";

// Check single resource
checkPermission(permissions, "user.1234.read"); // boolean

// Check multiple resources (batch)
const results = checkPermissions(permissions, [
  "user.1234.read",
  "admin.panel",
  "discussion.abc.read",
]);
// [true, false, true]

// Get all matching permissions (for debugging)
const matches = getMatchingPermissions(permissions, "discussion.abc.read");

// Check for root access
hasRootAccess(permissions); // boolean

Managing Permissions

import {
  mergePermissions,
  sortPermissions,
  filterPermissionsByType
} from "@fimbul-works/acl";

// Merge multiple permission arrays (with deduplication)
const userPerms = createPermissions([["allow", "user.*.read"]]);
const adminPerms = createPermissions([["allow", "*"]]);
const merged = mergePermissions(userPerms, adminPerms);

// Sort by precedence (deny before allow, general before specific)
const sorted = sortPermissions(permissions);

// Filter by type
const allowPerms = filterPermissionsByType(permissions, "allow");
const denyPerms = filterPermissionsByType(permissions, "deny");

Serialization

import {
  serializePermissions,
  deserializePermissions
} from "@fimbul-works/acl";

// Serialize to string
const data = serializePermissions(permissions);
// "deny:admin.*,allow:user.*.read"

// Deserialize from string
const restored = deserializePermissions(data);

Permission Math

import {
  intersectPermissions,
  subtractPermissions
} from "@fimbul-works/acl";

// Find common permissions across multiple arrays
const common = intersectPermissions(roleA, roleB, roleC);

// Subtract permissions (what's in A but not in B)
const diff = subtractPermissions(adminPerms, userPerms);

Validation & Debugging

import {
  validatePermissionPattern,
  expandPermissions
} from "@fimbul-works/acl";

// Validate pattern without throwing
const validation = validatePermissionPattern("user.*.read");
// { valid: true }

const bad = validatePermissionPattern("");
// { valid: false, error: "Permission pattern cannot be empty" }

// Expand wildcards to see what they match
const expanded = expandPermissions(permissions, [
  "user.1234.read",
  "admin.panel",
  "discussion.abc.read",
]);
// [
//   { permission: {...}, matches: ["user.1234.read"] },
//   { permission: {...}, matches: ["admin.panel"] }
// ]

Real-World Examples

Web Application ACL

// Define role-based permissions
const roles = {
  guest: createPermissions([
    ["allow", "*.public.view"],
  ]),

  user: createPermissions([
    ["allow", "user.*.read"],
    ["allow", "user.*.edit"],
    ["allow", "discussion.*.read"],
    ["allow", "discussion.*.comment"],
    ["deny", "user.*.delete"],
  ]),

  moderator: createPermissions([
    ["allow", "discussion.*.read"],
    ["allow", "discussion.*.moderate"],
    ["allow", "discussion.*.delete"],
    ["allow", "user.*.read"],
    ["allow", "user.*.suspend"],
    ["deny", "admin.*"],
  ]),

  admin: createPermissions([
    ["allow", "*"],  // Root access
  ]),
};

// Check user permissions
function canAccess(userRole: keyof typeof roles, resource: string): boolean {
  return checkPermission(roles[userRole], resource);
}

canAccess("user", "user.1234.edit");             // true
canAccess("user", "user.1234.delete");           // false
canAccess("moderator", "discussion.123.delete"); // true
canAccess("guest", "admin.panel");               // false

JWT Token Integration

import jwt from "jsonwebtoken";
import { createPermissions, serializePermissions, deserializePermissions } from "@fimbul-works/acl";

// When creating JWT
const token = jwt.sign({
  sub: "user123",
  permissions: serializePermissions(createPermissions([
    ["allow", "user.*.read"],
    ["allow", "user.1234.*"],
  ])),
}, "secret");

// When verifying JWT
const decoded = jwt.verify(token, "secret") as { permissions: string };
const permissions = deserializePermissions(decoded.permissions);

if (checkPermission(permissions, "user.1234.edit")) {
  // Grant access
}

Database Storage

import { createPermissions, serializePermissions, deserializePermissions } from "@fimbul-works/acl";

// Save to database
await db.users.update({
  where: { id: 123 },
  data: {
    permissions: serializePermissions(userPermissions),
  },
});

// Load from database
const user = await db.users.findUnique({ where: { id: 123 } });
const permissions = deserializePermissions(user.permissions);

Combining Multiple Roles

import { mergePermissions, checkPermission } from "@fimbul-works/acl";

// User can have multiple roles
const userRoles = [
  createPermissions([["allow", "content.*.read"]]),    // Editor
  createPermissions([["allow", "content.*.publish"]]), // Publisher
  createPermissions([["deny", "content.*.delete"]]),   // Cannot delete
];

// Merge all roles
const merged = mergePermissions(...userRoles);

checkPermission(merged, "content.123.read");    // true
checkPermission(merged, "content.123.publish"); // true
checkPermission(merged, "content.123.delete");  // false (deny wins)

Performance

The library is optimized for performance:

  • Automatic sorting - Permissions are sorted by precedence for early-exit optimization
  • O(n) complexity - Linear time permission checking
  • Efficient pattern matching - Pre-split pattern segments
  • Batch operations - Check multiple resources in one call

Benchmark with 1000 permissions:

// Checking 100 resources
const start = Date.now();
for (let i = 0; i < 100; i++) {
  checkPermission(permissions, `resource.${i}.action`);
}
console.log(`${Date.now() - start}ms`); // Typically < 10ms

API Reference

Core Functions

  • createPermission(type, pattern) - Create single permission
  • createPermissions(rules) - Create multiple permissions
  • addPermission(permissions, type, pattern) - Add permission
  • removePermission(permissions, type, pattern) - Remove permission
  • checkPermission(permissions, resource) - Check single resource
  • checkPermissions(permissions, resources) - Check multiple resources
  • mergePermissions(...arrays) - Merge and deduplicate
  • sortPermissions(permissions) - Sort by precedence
  • hasRootAccess(permissions) - Check for root access
  • getMatchingPermissions(permissions, resource) - Get all matches

Utility Functions

  • serializePermissions(permissions) - Convert to string
  • deserializePermissions(data) - Parse from string
  • intersectPermissions(...arrays) - Find common permissions
  • subtractPermissions(base, remove) - Remove permissions
  • filterPermissionsByType(permissions, type) - Filter by type
  • expandPermissions(permissions, resources) - Show pattern matches
  • validatePermissionPattern(pattern) - Validate without throwing

TypeScript Support

This library is written in TypeScript and provides excellent type inference:

import { Permission, PermissionType } from "@fimbul-works/acl";

const type: PermissionType = "allow"; // "allow" | "deny"
const permission: Permission = {
  type: "allow",
  pattern: "user.*.read",
  parts: ["user", "*", "read"],
  isRoot: false,
};

License

MIT License - See LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.


Built with ⚡ by FimbulWorks