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

@polymech/acl

v0.2.0

Published

Role-based access control (RBAC) library for Node.js, featuring hierarchical roles, capabilities, and Virtual Filesystem (VFS) integration.

Readme

@polymech/acl

npm version TypeScript

Role-based access control (RBAC) library inspired by Zend_ACL.
Source Repository: polymech/mono | Used in Filebrowser at https://service.polymech.info/.

Pure ESM, async/await, zero runtime dependencies (except pino for optional logging).

Supabase integration? See Supabase RLS Mirror — VFS Guide
Pure Postgres? See Postgres RLS — VFS Guide

Source Layout

src/
├── Acl.ts                  # Core ACL class
├── interfaces.ts           # Type definitions & backend interface
├── index.ts                # Barrel export
├── data/
│   ├── MemoryBackend.ts    # In-memory backend (default)
│   └── FileBackend.ts      # JSON file backend (dev/testing)
├── vfs/                    # Virtual filesystem extensions
│   ├── AclVfsClient.ts     # Guarded VFS bindings
│   ├── DecoratedVfsClient.ts # Extensible client wrapper
│   └── vfs-acl.ts          # VFS permission settings loader
└── acl.test.ts             # Functional tests (vitest)

Concepts

| Term | Description | |------|-------------| | User | An identifier (string or number) representing a subject | | Role | A named group of permissions assigned to users | | Resource | Something being protected (e.g. "posts", "settings") | | Permission | An action on a resource (e.g. "read", "write", "delete") | | Parent | Roles can inherit permissions from parent roles |

Wildcard "*" grants all permissions on a resource.

Quick Start

import { Acl, MemoryBackend } from '@polymech/acl';

const acl = new Acl(new MemoryBackend());

// Define permissions
await acl.allow('viewer', 'posts', 'read');
await acl.allow('editor', 'posts', ['read', 'write', 'delete']);
await acl.allow('admin', 'settings', '*');

// Assign roles to users
await acl.addUserRoles('alice', 'editor');
await acl.addUserRoles('bob', 'viewer');

// Check access
await acl.isAllowed('alice', 'posts', 'write');   // true
await acl.isAllowed('bob', 'posts', 'write');     // false
await acl.isAllowed('bob', 'posts', 'read');      // true

Role Hierarchy

Roles can inherit from parents. A child role gets all permissions of its ancestors.

await acl.allow('viewer', 'docs', 'read');
await acl.allow('editor', 'docs', 'write');
await acl.allow('admin', 'docs', 'admin');

// editor inherits from viewer, admin inherits from editor
await acl.addRoleParents('editor', 'viewer');
await acl.addRoleParents('admin', 'editor');

await acl.addUserRoles('carol', 'admin');

await acl.isAllowed('carol', 'docs', 'read');   // true  (from viewer)
await acl.isAllowed('carol', 'docs', 'write');  // true  (from editor)
await acl.isAllowed('carol', 'docs', 'admin');  // true  (own)

Batch Grants

Use the array syntax to define complex permission sets at once:

await acl.allow([
  {
    roles: 'moderator',
    allows: [
      { resources: 'posts',    permissions: ['read', 'edit', 'flag'] },
      { resources: 'comments', permissions: ['read', 'delete'] },
    ],
  },
  {
    roles: 'author',
    allows: [
      { resources: 'posts', permissions: ['read', 'create'] },
    ],
  },
]);

Querying Permissions

// All permissions a user has on given resources
const perms = await acl.allowedPermissions('alice', ['posts', 'settings']);
// → { posts: ['read', 'write', 'delete'], settings: [] }

// Which resources does a role have access to?
const resources = await acl.whatResources('editor');
// → { posts: ['read', 'write', 'delete'] }

// Which resources grant a specific permission?
const writable = await acl.whatResources('editor', 'write');
// → ['posts']

Removal

// Remove specific permissions
await acl.removeAllow('editor', 'posts', 'delete');

// Remove all permissions for a resource from a role
await acl.removeAllow('editor', 'posts');

// Remove a role entirely
await acl.removeRole('editor');

// Remove a resource from all roles
await acl.removeResource('posts');

// Remove a role from a user
await acl.removeUserRoles('alice', 'editor');

File Backend (Dev/Testing)

FileBackend extends MemoryBackend with JSON persistence:

import { Acl, FileBackend } from '@polymech/acl';

const backend = new FileBackend('./acl-data.json');
backend.read();                     // load from disk

const acl = new Acl(backend);
await acl.allow('admin', 'all', '*');
await acl.addUserRoles('root', 'admin');

backend.write();                    // persist to disk

Logging

Pass a pino logger as the second constructor argument:

import pino from 'pino';
import { Acl, MemoryBackend } from '@polymech/acl';

const logger = pino({ level: 'debug' });
const acl = new Acl(new MemoryBackend(), logger);

await acl.allow('admin', 'posts', '*');
// logs: { roles: ['admin'], resources: ['posts'], permissions: ['*'] } allow

Custom Backend

Implement the IBackend interface to plug in any storage:

import type { IBackend, Value, Values } from '@polymech/acl';

class RedisBackend implements IBackend<RedisTx> {
  begin(): RedisTx { /* ... */ }
  async end(tx: RedisTx): Promise<void> { /* ... */ }
  async clean(): Promise<void> { /* ... */ }
  async get(bucket: string, key: Value): Promise<string[]> { /* ... */ }
  async union(bucket: string, keys: Value[]): Promise<string[]> { /* ... */ }
  async unions(buckets: string[], keys: Value[]): Promise<Record<string, string[]>> { /* ... */ }
  add(tx: RedisTx, bucket: string, key: Value, values: Values): void { /* ... */ }
  del(tx: RedisTx, bucket: string, keys: Values): void { /* ... */ }
  remove(tx: RedisTx, bucket: string, key: Value, values: Values): void { /* ... */ }
}

Real-World Example — VFS Per-User Folder Permissions

Fine-grained, path-scoped access control for a virtual filesystem where each user owns a folder and can grant specific permissions to others — individually or via groups.

How It Works

Each user's VFS folder contains a vfs-settings.json that declares who can do what:

{
  "owner": "3bb4cfbf-318b-44d3-a9d3-35680e738421",
  "groups": [
    {
      "name": "team",
      "members": ["aaaaaaaa-...", "cccccccc-..."]
    },
    {
      "name": "viewers",
      "members": ["dddddddd-...", "ffffffff-..."]
    }
  ],
  "acl": [
    {
      "group": "team",
      "path": "/shared",
      "permissions": ["read", "write", "list", "mkdir", "delete"]
    },
    {
      "group": "team",
      "path": "/docs",
      "permissions": ["read", "list"]
    },
    {
      "group": "viewers",
      "path": "/docs",
      "permissions": ["read", "list"]
    },
    {
      "userId": "ffffffff-...",
      "path": "/shared",
      "permissions": ["read", "list"]
    }
  ]
}
  • Groups bundle users — grant once, apply to all members.
  • Direct user grants coexist with groups for individual overrides.
  • Path scoping — grants apply to a specific folder and its children (/shared, /docs).
  • The owner always gets * on / (entire tree).

Permission Model

| Permission | Operations | |------------|------------| | read | stat, readfile, exists | | list | readdir | | write | writefile, mkfile | | mkdir | mkdir | | delete | rmfile, rmdir | | rename | rename | | copy | copy | | * | All of the above — auto-granted to the folder owner |

Path-Scoped Resolution

When a user accesses /shared/reports/q1.pdf, the guard walks the resource chain upward:

vfs:<ownerId>:/shared/reports/q1.pdf   ← most specific
vfs:<ownerId>:/shared/reports
vfs:<ownerId>:/shared                  ← team group matches here ✓
vfs:<ownerId>:/

Access is granted if any level allows the operation.

Setup Flow

sequenceDiagram
    participant App
    participant Bridge as VFS ACL Bridge
    participant ACL as Acl Instance

    App->>Bridge: loadVfsSettings acl, userDir
    Bridge->>Bridge: Read vfs-settings.json
    Bridge->>ACL: allow owner role, resource, wildcard
    Bridge->>ACL: addUserRoles owner, owner role
    loop Each group ACL entry
        Bridge->>ACL: allow group role, resource, permissions
        loop Each group member
            Bridge->>ACL: addUserRoles member, group role
        end
    end
    loop Each direct ACL entry
        Bridge->>ACL: allow grant role, resource, permissions
        Bridge->>ACL: addUserRoles grantee, grant role
    end
    Bridge-->>App: Return settings

Code Example

import { Acl, MemoryBackend, AclVfsClient, loadVfsSettings } from '@polymech/acl';

// 1. Create ACL and load settings from the user's folder
const acl = new Acl(new MemoryBackend());
const userDir = './data/vfs/3bb4cfbf-318b-44d3-a9d3-35680e738421';
await loadVfsSettings(acl, userDir);

// 2. Create a guarded client for a specific caller
const ownerId = '3bb4cfbf-318b-44d3-a9d3-35680e738421';
const callerId = 'aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb'; // team member

const client = new AclVfsClient(acl, ownerId, callerId, {
  root: userDir,
});

// 3. Allowed — team has read+list on /docs
const files = await client.readdir('docs');           // ✓ works
const exists = await client.exists('docs/readme.txt'); // ✓ works

// 4. Allowed — team has full access on /shared
await client.writefile('shared/notes.txt', 'hello');  // ✓ works
await client.mkdir('shared/reports');                  // ✓ works

// 5. Denied — team has no access on /private
await client.readdir('private');                      // ✗ throws EACCES
await client.writefile('private/x.txt', '...');       // ✗ throws EACCES

// 6. Denied — team has read-only on /docs, no write
await client.writefile('docs/hack.txt', '...');       // ✗ throws EACCES

Test Fixtures

tests/
├── vfs/root/
│   ├── 3bb4cfbf-…/
│   │   ├── vfs-settings.json          # Path-scoped direct grants
│   │   ├── docs/readme.txt
│   │   ├── shared/data.txt
│   │   └── private/secret.txt
│   ├── groups-test/
│   │   └── vfs-settings.json          # Group grants + mixed direct
│   ├── edge-cases/                    # Edge case fixtures
│   │   └── vfs-settings.json
│   └── anon-test/                     # Anonymous access fixtures
│       └── vfs-settings.json
├── vfs-acl.e2e.test.ts                # 26 permission boundary tests
├── vfs-acl-fs.e2e.test.ts             # 24 real filesystem tests
├── vfs-acl-paths.e2e.test.ts          # 34 per-path nested tests
├── vfs-acl-groups.e2e.test.ts         # 27 groups e2e tests
├── vfs-acl-edge.e2e.test.ts           # 18 edge case tests  
├── vfs-acl-decorated.e2e.test.ts      # 15 decorated VFS tests
└── vfs-acl-anonymous.e2e.test.ts      # 8 anonymous access tests

Scripts

npm run build        # Compile TypeScript
npm run dev          # Watch mode
npm run test:core    # Core ACL tests (23)
npm run test:all     # Full suite (175 tests)
npm run lint         # ESLint

References

1. Virtual Filesystems and OS Integration

2. Browser and PWA Primitives

3. Decentralized Storage, Sync, and Collaboration

4. Local‑First Data Structures and Collaboration Engines

5. Identity and Capability‑Based Access


This wonderful package has been brought to you by the people who get shit done :)