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

functional-acl

v0.6.0

Published

A functional implementation of access control lists (ACLs)

Readme

Functional Access Control Lists (node-functional-acl)

About

From Wikipedia:

An ACL specifies which users or system processes are granted access to objects, as well as what operations are allowed on given objects. Each entry in a typical ACL specifies a subject and an operation.

Functional ACL are an implementation of this concept with functional programming. It makes heavy use of higher-order functions as predicates to model user permissions.

What it is not

  • a role-based permission system (though it can model one)
  • a database wrapper (you have to model that part on your own)

Installation

Get it as a node package from npm:

npm install functional-acl

Import it, and off you go!

Terminology / Tutorial

Context

An object that describes the situation you are checking permissions for. This usually contains one or more of:

  • the acting user (user / subject)
  • the action to perform (action / operation)
  • the target object (target / object)
  • anything you need to identify whether or not to allow/deny the request

Predicate

A function that checks whether the context (or part of it) matches a certain condition. Returns true or false. Examples:

  • admins = (context) => context.user.isAdmin
  • guests = (context) => !context.user
  • owner = (context) => context.user == context.target.owner

Rule

A function that returns one of true (allow), false (deny) or null (proceed with next rule) for a context. These rules can be easily built from predicates using the allow(predicate) and deny(predicate) functions. If the predicate matches on the context, they return the appropriate boolean value, and null otherwise.

Multiple rules can be combined using the combineRules(...rules) function. These are then tested in order, the first rule that matches decides the result (true or false). If no rule matches, the combined rule returns null.

API

not(predicate) => predicate

Create a predicate that matches if the original predicate did not match.

invert(rule) => rule

Create a rule that allows if the original rule would deny, and vice versa. Does not decide if the original rule wouldn't.

always() => true

Predicate that always matches.

never() => false

Predicate that never matches.

every(...predicates) => predicate

Create a predicate that matches if all predicates match.

some(...predicates) => predicate

Create a predicate that matches if at least one of the predicates matches.

none(...predicates) => predicate

Create a predicate that matches if none of the predicates matches

allow(predicate) => rule

Create a rule that returns true if the predicate matches.

deny(predicate) => rule

Create a rule that returns false if the predicate matches.

combineRules(...rules) => rule

Create a rule that returns the result of the first matching rule, or null if none matches

forceDecisionIf(predicate, rule, undecidedResult = false) => rule

Create a rule that is triggered only if the predicate matches, and if the rule does not decide, return the undecidedResult.

enforce(rule, context) => undefined

Throws an ACLRejectionError if the rule rejects the context, or does not decide on the context (i.e. returns null).

Instead of a try/catch switch, you can manually check a rule simply by calling it with a context (it is a function after all, hence this package's name). This would look something like:

const myRule = combineRules(...);
const myContext = { user: ..., operation: ..., foo: ... };

if (!myRule(myContext)) {
    console.warn('You are denied access.');
} else {
    performSuperSecretStuff();
}

Complete example

import {combineRules, deny, allow} from '../src';

const admins = ({user}) => user && user.isAdmin;
const guests = ({user}) => !user;
const reading = ({operation}) => operation === 'read';
const writing = ({operation}) => operation === 'write';

const restricted = combineRules(
    deny(guests),       // guests may not ever read
    allow(admins),      // admins may do everything
    allow(reading),     // everyone else may read
);

// now you can check permissions simply by calling
const context = {user: myUser, operation: 'read'};
const allowed = restricted(context);

// or enforce a permission (throw ACLRejectedError otherwise)
enforce(restricted, context);

Express middleware

There is a helper in functional-acl/express that creates a customized middleware factory. Sounds complicated? It's not! Here we go:

import aclExpress from 'functional-acl/express';

// our middleware factory is called 'applyAcl'
const applyACL = aclExpress({
    deriveContext(req) { return { user: req.user; }},
    createDirectContext(operation) { return { operation }},
});

app.use(authMiddleware); // inject req.user

// check "myRule" with {user: req.user, operation: 'read'}
app.get('/', applyACL(myRule, 'read'), myRoute);

You find a full working example in the examples directory.

The function aclExpress creates a middleware factory using the following options:

deriveContext: (req, res, next) => context

Used to derive a partial context from the request. This can for example be used to supply the current user in the context. Make sure to plug in an authorization middleware (such as passport) somewhere, so you can access the user (e.g. req.user) here.

createDirectContext: (...args) => context

Used to extract a partial context from the arguments passed into the resulting middleware after the rule. In the example above, there is only one expected argument after the rule, which is the operation. This is then merged into the context at the key operation. You can see the value 'read' being passed in for the example route.

onDeny: (rule, context, req, res, next) => undefined

Used by the default applyRule as an error handler. Defaults to throwing an ACLRejectionError.

applyRule: (rule, directContext, extractedContext, req, res, next) => undefined

Advanced: Overwrite how to apply the rule in the middleware.

By default, it builds a context from the direct and extracted partial contexts (direct context overwrites extracted context) and applies the rule on it. If that fails, onDeny is called.