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

native-access-control

v0.0.2

Published

Zero-overhead, type-safe Role & Attribute-Based Access Control (RBAC/ABAC) for React, React Native, Node.js, and TypeScript.

Readme

Zero-overhead, type-safe Role & Attribute-Based Access Control (RBAC & ABAC) with role inheritance, multi-tenant data scoping, and declarative UI guards.

Universal: works across React, React Native / Expo, Next.js, Node.js, and any TypeScript project.

npm version License: MIT Dependencies: 0 Type-Safe


Features

  • 🚀 Zero Dependencies: Lightweight, pure TypeScript core.
  • 🎯 Full Type-Safety: Compile-time autocompletion for Roles, Resources, and Actions.
  • 🌲 Role Hierarchy: Roles inherit parent permissions (owner ➔ admin ➔ editor ➔ viewer).
  • 🚫 Explicit Denials: Deny rules override allow rules for security-critical restrictions.
  • ⚡ Dynamic ABAC: Context-aware rule predicates (context) => boolean.
  • 🏢 Multi-Tenant Data Scoping: scopeRecords() for tenant (tenantId) and ownership (userId) filtering.
  • 📱 Declarative UI Guards: <Can>, <Cannot>, <Protect>, and createRoleGuard().

Installation

npm install native-access-control

Note: react and react-native are optional peer dependencies. If using on the backend or in Node.js, no React packages are needed.


Usage Guide

1. Define Policies & Create Suite

Create an access.ts file in your project to define your application's roles, resources, actions, and policies:

// access.ts
import { createAccessControl } from 'native-access-control';

export type Role = 'owner' | 'admin' | 'editor' | 'viewer';
export type Resource = 'post' | 'comment' | 'billing' | 'user';
export type Action = 'create' | 'read' | 'edit' | 'delete' | 'publish';

export interface AppContext {
  userId?: string;
  post?: { authorId: string; isLocked?: boolean };
}

export const {
  AccessProvider,
  useAccess,
  Can,
  Cannot,
  Protect,
  createRoleGuard,
  scopeRecords,
  engine,
} = createAccessControl<Role, Resource, Action, AppContext>({
  policies: {
    // Wildcard matches everything
    admin: ['*'],

    // Resource wildcards & ABAC rules
    editor: [
      'post:*',
      'comment:*',
      {
        action: 'delete',
        resource: 'post',
        effect: 'deny',
        condition: (ctx) => Boolean(ctx.post?.isLocked), // Explicit deny rule
      },
    ],

    // Read-only role
    viewer: ['post:read', 'comment:read'],
  },

  // Role inheritance (admin inherits editor, which inherits viewer)
  roleHierarchy: {
    owner: ['admin'],
    admin: ['editor'],
    editor: ['viewer'],
  },

  // Roles that automatically bypass all checks
  superRoles: ['owner'],
});

// Reusable role-specific guard components
export const AdminOnly = createRoleGuard('admin');
export const EditorOrAdmin = createRoleGuard(['admin', 'editor']);

2. Wrap App with AccessProvider

Provide the active user's roles and superuser status at the root of your application (e.g. App.tsx, layout.tsx, or _app.tsx):

import { AccessProvider } from './access';

function App() {
  const userRoles = ['editor']; // Loaded from your auth / session
  const isOwner = false;

  return (
    <AccessProvider roles={userRoles} isSuperUser={isOwner}>
      <MainNavigator />
    </AccessProvider>
  );
}

3. Programmatic Checks with useAccess()

Use the useAccess() hook inside any React or React Native component for imperative logic:

import { useAccess } from './access';

export function PostActions({ post, currentUser }) {
  const { can, cannot, hasRole, roles } = useAccess();

  const handleEdit = () => {
    if (cannot('edit', 'post', { post })) {
      alert('You do not have permission to edit this post.');
      return;
    }
    navigateToEdit(post.id);
  };

  return (
    <div>
      {can('edit', 'post', { post }) && (
        <button onClick={handleEdit}>Edit Post</button>
      )}

      {hasRole('admin') && (
        <button onClick={() => deletePost(post.id)}>Admin Delete</button>
      )}
    </div>
  );
}

Hook Methods:

  • can(action, resource, context?): Returns true if permitted.
  • cannot(action, resource, context?): Returns true if forbidden.
  • canAll(checks, context?): Returns true if all checks pass.
  • canAny(checks, context?): Returns true if at least one check passes.
  • hasRole(roleOrRoles): Checks if user has a role (respects inheritance).
  • hasAllRoles(roles): Checks if user has all specified roles.
  • roles: Current active roles.
  • effectiveRoles: Active roles plus all transitively inherited roles.

4. Declarative UI Guards (<Can> & <Cannot>)

Standard Guard

Renders children only when the permission check passes:

import { Can } from './access';

<Can action="publish" resource="post">
  <PublishButton />
</Can>

Inverse Guard (<Cannot>)

Renders children when an action is not permitted (great for upgrade prompts, tooltips, or read-only indicators):

import { Cannot } from './access';

<Cannot action="delete" resource="post" context={{ post }}>
  <span className="badge">Locked / Read Only</span>
</Cannot>

Render Prop Pattern

Pass a function as children to control element state (e.g. disabling buttons):

<Can action="edit" resource="post" context={{ post }}>
  {(allowed) => (
    <button disabled={!allowed} onClick={onEdit}>
      {allowed ? 'Edit' : 'Edit (Locked)'}
    </button>
  )}
</Can>

Role Checks & Fallbacks

<Can roles={['admin', 'editor']} fallback={<p>Editors only</p>}>
  <EditorPanel />
</Can>

5. Screen & Layout Protection (<Protect>)

Protect entire routes, screens, or navigation tabs:

import { Protect } from './access';

export default function SettingsScreen() {
  return (
    <Protect
      roles="admin"
      fallback={<AccessDeniedView />}
      loadingFallback={<Spinner />}
    >
      <AdminSettingsList />
    </Protect>
  );
}

Or protect based on capabilities:

<Protect action="read" resource="billing" fallback={<UnauthorizedScreen />}>
  <BillingDashboard />
</Protect>

6. Partition & Ownership Data Scoping (scopeRecords)

Filter raw arrays in state, services, or APIs to ensure restricted roles only see their permitted records (by workspace, org, or ownership), while privileged users see all records:

import { scopeRecords } from './access';

// Scopes records based on partition/scope and ownership:
const visiblePosts = scopeRecords(allPosts, {
  // 1. Scope partition isolation (workspace, org, account, project, etc.)
  scopeId: activeWorkspaceId,
  scopeKey: 'workspaceId',

  // 2. Ownership check (applied only if restricted)
  userId: currentUserId,
  userKey: 'authorId', // defaults to 'userId'
  isRestrictedRole: !currentUser.isAdmin,

  // 3. Optional custom predicate (ABAC)
  predicate: (post) => !post.isArchived,
});

7. Node.js, Express & Backend API Usage

You can use the engine directly on your backend with zero React dependencies:

import { createAccessEngine } from 'native-access-control';

const engine = createAccessEngine({
  policies: {
    admin: ['*'],
    editor: ['post:*'],
    viewer: ['post:read'],
  },
  roleHierarchy: {
    admin: ['editor'],
    editor: ['viewer'],
  },
});

// Express route handler
app.delete('/api/posts/:id', (req, res) => {
  const evaluator = engine.buildEvaluator(req.user.roles);

  if (evaluator.cannot('delete', 'post')) {
    return res.status(403).json({ error: 'Forbidden' });
  }

  // Proceed with deletion...
});

8. Database Query Filtering (buildScopeFilter)

Generate a WHERE filter object for databases or ORMs (Prisma, Supabase, Drizzle, Mongo):

import { buildScopeFilter } from 'native-access-control';

const where = buildScopeFilter({
  scopeId: activeWorkspaceId,
  scopeKey: 'workspace_id',
  userId: currentUserId,
  userKey: 'author_id',
  isRestrictedRole: !isAdmin,
});

// Prisma:   db.document.findMany({ where })
// Supabase: supabase.from('documents').select().match(where)

9. Fail-Fast Assertions (assertCan & assertHasRole)

Cleanly guard mutations, handlers, or background services without repetitive if (cannot(...)) blocks:

import { assertCan, assertHasRole } from 'native-access-control';

async function deleteContract(id: string) {
  // Automatically throws AccessDeniedError (403) if unauthorized
  assertCan(evaluator, 'delete', 'contract');
  await api.deleteContract(id);
}

10. Route & Navigation Protection (checkRouteAccess)

Check navigation routes in Expo Router (_layout.tsx), React Navigation, or Next.js:

import { checkRouteAccess } from 'native-access-control';

const { allowed, redirectTo } = checkRouteAccess(pathname, userRoles, {
  '/admin/*': ['admin'],
  '/reports': ['admin', 'staff'],
  '/public': '*',
}, { fallbackRoute: '/dashboard' });

if (!allowed) {
  return <Redirect href={redirectTo} />;
}

11. Sensitive Field Masking (filterRecord)

Filter unauthorized fields before returning data to the client:

import { filterRecord } from 'native-access-control';

const safeProperty = filterRecord(property, {
  allowedFields: isRestricted ? ['id', 'address', 'rent'] : '*',
  omitFields: isRestricted ? ['owner_bank_details', 'internal_notes'] : [],
});

12. Database, ORM & Framework Adapters

Zero-dependency adapters in native-access-control/adapters:

  • Drizzle ORM (toDrizzleWhere): Generate composite conditions combining partition scope and user ownership.
  • Prisma Client (toPrismaWhere, createPrismaScopeExtension): Query where clause builder and client extension for auto-scoping.
  • MongoDB / Mongoose (toMongoFilter): Generate MongoDB query objects.
  • Supabase (applySupabaseScope, toSupabaseMatch): Direct query builder chaining and match filters.
  • PostgreSQL (toPostgresWhere, generateRlsPolicy): Parameterized $1, $2 SQL and automated RLS SQL policy generation.
  • Next.js (protectAction, protectRouteHandler): Guard wrappers for App Router Server Actions and route handlers.
  • Express / Connect (expressGuard): Middleware with automatic 403 JSON responses.
  • Hono (honoGuard): Edge-ready middleware for Cloudflare Workers, Bun, and Node.
  • tRPC (createTrpcGuard): Middleware for procedure authorization.
import {
  toDrizzleWhere,
  toPrismaWhere,
  createPrismaScopeExtension,
  protectAction,
  expressGuard,
  honoGuard,
  createTrpcGuard,
} from 'native-access-control/adapters';

API Summary

| Export | Type | Description | |---|---|---| | createAccessControl(config) | Factory | Creates typed React suite (AccessProvider, useAccess, Can, Protect, engine) | | createAccessEngine(config) | Function | Creates standalone core engine (RBAC/ABAC) | | <AccessProvider> | Component | React Context Provider for roles & permissions | | useAccess() | Hook | Provides can(), cannot(), hasRole(), roles | | <Can> | Component | Conditional render component based on permissions or roles | | <Cannot> | Component | Inverted conditional render component | | <Protect> | Component | Screen/route guard with fallback support | | createRoleGuard(roles) | Helper | Creates custom single/multi-role components (e.g. <AdminOnly>) | | scopeRecords(records, options) | Function | Filters in-memory arrays by scope partition and ownership | | buildScopeFilter(options) | Function | Generates database query WHERE filters (Prisma/Drizzle/Supabase) | | filterRecord(record, options) | Function | Masks/omits sensitive object fields | | assertCan(eval, action, res) | Function | Throws AccessDeniedError if capability check fails | | assertHasRole(eval, roles) | Function | Throws AccessDeniedError if role check fails | | checkRouteAccess(path, roles, routes) | Function | Validates route permissions for Expo Router / Next.js | | applySupabaseScope(query, options) | Function | Injects .eq() constraints directly into Supabase queries | | toSupabaseMatch(options) | Function | Generates Supabase .match() filter dictionary | | toPostgresWhere(options) | Function | Generates parameterized $1, $2 SQL clause and parameter array | | generateRlsPolicy(options) | Function | Generates PostgreSQL / Supabase Row-Level Security SQL | | AccessDeniedError | Error | Standard 403 error class with action/resource context |