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

@merncloud/permission-resolver

v1.0.15

Published

Reusable RBAC + ABAC permission resolver for Node.js

Downloads

111

Readme

@merncloud/permission-resolver

A lightweight, pluggable RBAC + ABAC permission resolver for Node.js and frontend apps.

✅ Role-Based Access
✅ Attribute-Based Access
✅ JSON Logic conditions
✅ Flattened permissions
✅ Works with any DB
✅ Works in Node.js, NestJS, React, etc.


🔧 Installation

npm install @merncloud/permission-resolver

📦 Backend Usage

1. Initialize with Static Rules (e.g. from a local TS file)

// auth/customRules.ts
export const customPermissionRules = [
  {
    resource: "document",
    action: "read",
    allowedRoles: ["editor", "viewer"],
  },
  {
    resource: "document",
    action: "write",
    attributeCondition: {
      ">=": [{ var: "subject.attributes.level" }, 2],
    },
  },
];
// index.ts
import { PermissionService } from "@merncloud/permission-resolver";
import { customPermissionRules } from "./auth/customRules";

PermissionService.init(customPermissionRules);
const resolver = PermissionService.getInstance();

app.get("/permissions", async (req, res) => {
  try {
    const permissions = await resolver.getRules();
    res.json(permissions);
  } catch (error) {
    res.status(500).json({ error: "Failed to fetch permissions" });
  }
});

2. Initialize with Rules from Database

import { Pool } from "pg";
import {
  createResolverFromSource,
  PermissionService,
} from "@merncloud/permission-resolver";

const pool = new Pool({
  /* your pg config */
});

const resolver = await createResolverFromSource(async () => {
  const result = await pool.query("SELECT * FROM permissions");
  return result.rows.map((row) => ({
    resource: row.resource,
    action: row.action,
    allowedRoles: row.allowed_roles,
    attributeCondition: row.attribute_condition,
  }));
});

PermissionService.init(resolver.getRules());

///// You can create a RbacService class and use it like this

import { RowDataPacket } from "mysql2";
import { getDBPool } from "./db";
import { PermissionService } from "@merncloud/permission-resolver";

class RbacSingleton {
  private resolver: any = null;
  private isInitialized = false;
  private initPromise: Promise<any> | null = null;

  async init() {
    if (this.isInitialized) return this.resolver;
    if (this.initPromise) return this.initPromise;

    this.initPromise = this.doInit();
    return this.initPromise;
  }

  private async doInit() {
    try {
      // Fetch permissions from database
      const pool = getDBPool();
      const [rows] = await pool.query("SELECT * FROM permissions");
      const result = rows as RowDataPacket[];

      // Transform database rows to permission rules format
      const permissionRules = result.map((row) => ({
        resource: row.resource,
        action: row.action,
        allowedRoles: row.allowed_roles,
        attributeCondition: row.attribute_condition,
      }));

      // Initialize PermissionService with rules
      PermissionService.init(permissionRules);
      this.resolver = PermissionService.getInstance();
      this.isInitialized = true;

      console.log(
        "RBAC singleton initialized with",
        permissionRules.length,
        "rules"
      );
      return this.resolver;
    } catch (error) {
      this.initPromise = null; // Reset promise on error
      console.error("Failed to initialize RBAC singleton:", error);
      throw error;
    }
  }

  getResolver() {
    if (!this.isInitialized) {
      throw new Error("RBAC not initialized. Call rbacInstance.init() first.");
    }
    return this.resolver;
  }

  async reload() {
    this.isInitialized = false;
    this.initPromise = null;
    this.resolver = null;
    return await this.init();
  }
}

// Export singleton instance
export const rbacInstance = new RbacSingleton();

// index.ts
import { rbacInstance } from "./your-path/RbacSingleton";

await rbacInstance.init();

/// auth service
import { rbacInstance } from "../../../config/RbacSingleton";

export class AuthService {
  constructor() {}

  async getPermissions() {
    const resolver = rbacInstance.getResolver();
    const permissions = await resolver.getRules();
    return permissions;
  }
}

/// auth controller
import { Request, Response } from "express";
import { AuthService } from "../services/auth.service";

export class AuthController {
  constructor(private readonly authService: AuthService) {}

  getPermissions = async (req: Request, res: Response) => {
    const permissions = await this.authService.getPermissions();
    res.json(permissions);
  };
}

DB Table

3. Create Middleware for Access Control

function canAccess(resourceType: string, action: string) {
  return (req, res, next) => {
    const resolver = PermissionService.getInstance();

    const subject = {
      id: req.user.id,
      roles: req.user.roles,
      attributes: req.user.attributes,
    };

    const resource = {
      id: req.params.id,
      type: resourceType,
      attributes: req.resourceAttributes, // e.g. loaded from DB
    };

    if (resolver.can(subject, resource, action)) {
      return next();
    } else {
      return res.status(403).json({ error: "Forbidden" });
    }
  };
}

// Example usage
app.get("/documents/:id", canAccess("document", "read"), (req, res) => {
  res.json({ message: "You can read this document" });
});

🖥️ Frontend Usage (React / SPA)

// index.tsx
import { initPermissionClient } from "@merncloud/permission-resolver/frontend";
import { apiClient } from "./services/apiService";

await initPermissionClient({
  fetchRules: () => apiClient.get("/permissions"),
});

📥 Use in React Component

import { flatten } from "@merncloud/permission-resolver";
import { getPermissionClient } from "@merncloud/permission-resolver/frontend";

const subject = {
  id: "1",
  roles: ["user"],
  attributes: {
    team: "alpha",
    level: 4,
    region: "north",
  },
};

const resource = {
  id: "1",
  type: "document",
  attributes: {
    team: "alpha",
    region: "north",
  },
};

const Component = () => {
  const canRead = getPermissionClient().can(subject, resource, "read");
  const capabilities = flatten(subject, resource);

  return (
    <>
      {canRead && <p>You can read the document</p>}
      <p>Capabilities: {JSON.stringify(capabilities)}</p>
    </>
  );
};

🔁 Flattened Permissions

You can convert permissions into a flat list like:

["document:read", "document:write"];

Using:

import { flatten } from "@merncloud/permission-resolver";

const perms = flatten(subject, resource);

📐 Rule Format

Each rule supports:

{
  resource: string;
  action: string;
  allowedRoles?: string[];
  attributeCondition?: JsonLogicObject;
}

Example:

{
  resource: "document",
  action: "publish",
  allowedRoles: ["admin"],
  attributeCondition: {
    and: [
      { "==": [ { var: "subject.attributes.team" }, { var: "resource.attributes.team" } ] },
      { ">=": [ { var: "subject.attributes.level" }, 2 ] }
    ]
  }
}

🎯 Features

  • Works with any database
  • Supports complex JSON logic
  • Uses a singleton pattern for backend PermissionService
  • Frontend provides cached client with getPermissionClient()
  • Compatible with React, Express, NestJS, Next.js, etc.