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

@abolfazl-khalaj/permission-react

v0.2.1

Published

TypeScript-first React authorization primitives for permission-based UI access control

Readme

@abolfazl-khalaj/permission-react

Enterprise permission & authorization primitives for React applications.

TypeScript-first access control for buttons, menus, pages, and features — without scattering user.permissions.includes(...) across your codebase.

npm install @abolfazl-khalaj/permission-react

| | | | --- | --- | | Package | @abolfazl-khalaj/permission-react | | Peers | React ^18 \|\| ^19 | | License | MIT | | Output | Tree-shakeable ESM + CJS (sideEffects: false) |


Contents


1. Introduction

What is this package?

@abolfazl-khalaj/permission-react is a React adapter around a framework-agnostic permission engine (living under src/core).

You provide a subject (the current user: id, roles, permissions) and get:

| Capability | API | | --- | --- | | Imperative checks | can, hasRole, … | | Declarative UI gates | <Can /> | | Route-level gates | <PermissionRoute /> | | Async backend loading | permissionLoader, resolver | | Structured debugging | explain, debug |

Who is it for?

  • Frontend teams building dashboards, admin panels, and SaaS products
  • Apps where authorization rules come from a backend (JWT, session, API)
  • Codebases that need typed, testable, centralized permission logic

What problem does it solve?

Without a library, authorization usually becomes:

if (user.permissions.includes('user.delete')) {
  // show delete button
}

That pattern does not scale:

  • duplicated checks across components
  • no shared wildcard / role inheritance rules
  • hard to show loading / denied / disabled UI consistently
  • easy to flash unauthorized UI before permissions load
  • difficult to explain why a check failed

This package centralizes those decisions behind one provider and one engine.

Authentication vs authorization

| Concept | Question | Typical source | | --- | --- | --- | | Authentication | Who is this user? | Login, JWT, session cookie | | Authorization | What may this user do? | Roles, permissions, policies |

This library is an authorization layer. It assumes you already know who the user is.

RBAC, permissions, and ABAC

| Model | Idea | Status in this package | | --- | --- | --- | | RBAC | Access via roles (admin, editor) | Supported (hasRole, role inheritance) | | Permission-based | Access via fine-grained strings (invoice.approve) | Primary model (can, wildcards) | | ABAC | Access via attributes/conditions (ownerId === user.id) | Types prepared; evaluation not implemented yet |

Most production UIs start with permissions + roles. Attribute-based rules are a later evolution — the public types (PermissionRule, PermissionCondition, PermissionPolicy, PermissionQuery) exist so that path does not require an API rewrite.


2. Features

Implemented

| Feature | Description | | --- | --- | | PermissionProvider | Central authorization context and engine lifecycle | | Subject model | id, roles, permissions, attributes | | Permission engine | Framework-agnostic evaluation in src/core | | can / cannot / not | Sync permission checks | | canAny / canAll / hasNone | Multi-permission operators | | Role helpers | hasRole, hasAnyRole, hasAllRoles, hasInheritedRole | | check / explain | Structured results and decision reasons | | Async API | canAsync, status, loading, error, reload / cache clear | | <Can /> | Declarative UI with fallback, loading, errorFallback | | Modes | hidden | disable | readonly | | <PermissionRoute /> | Page gates with loading UI and optional redirect | | Wildcards | e.g. users.* | | Role hierarchy | Via roleDefinitions | | Custom matcher | permissionMatcher override | | Loaders & resolvers | permissionLoader, remote resolver, cacheTime | | Debug | Provider debug logging | | TypeScript | Strict public types | | React 18 / 19 | Peer support | | Tree-shaking | ESM + CJS, sideEffects: false |

Roadmap

| Item | Notes | | --- | --- | | Full ABAC evaluation | Types exist today | | Policy / condition runtime | Planned | | @abolfazl-khalaj/permission-core | Extract src/core for publish | | Next.js / Vue / Angular adapters | Planned | | SSR-safe navigation adapters | Beyond window.location |


3. Installation

Choose your package manager:

npm install @abolfazl-khalaj/permission-react
pnpm add @abolfazl-khalaj/permission-react
yarn add @abolfazl-khalaj/permission-react

Requirement: React ^18 || ^19.


4. Quick Start

Three steps to wire authorization into an app.

Step 1 — Create a subject (your user)

const user = {
  id: '1',
  roles: ['admin'],
  permissions: ['user.create', 'user.delete'],
};

This object is the authorization subject: the identity the engine evaluates against.

Step 2 — Wrap your app

import { PermissionProvider } from '@abolfazl-khalaj/permission-react';

export function Root() {
  return (
    <PermissionProvider subject={user}>
      <App />
    </PermissionProvider>
  );
}

PermissionProvider normalizes the subject, builds the engine/store, and exposes it through React context.

Step 3 — Check a permission

import { usePermission } from '@abolfazl-khalaj/permission-react';

function CreateUserButton() {
  const { can } = usePermission();

  if (!can('user.create')) {
    return null;
  }

  return <button type="button">Create user</button>;
}

| Line | Behavior | | --- | --- | | usePermission() | Reads the nearest provider context (throws if missing) | | can('user.create') | Asks the engine whether the subject holds that permission | | Conditional render | You control UI from the boolean result |

Declarative alternative:

import { Can } from '@abolfazl-khalaj/permission-react';

<Can permission="user.create">
  <button type="button">Create user</button>
</Can>

Expected: If the subject lacks user.create and mode is hidden (default), the button is not rendered.


5. PermissionProvider

Purpose

PermissionProvider is the root of the authorization tree.

  1. Accepts a subject (and optional async sources)
  2. Creates a framework-agnostic permission store/engine
  3. Publishes a stable API through context
  4. Keeps React components thin (no business rules in UI)

Props

| Prop | Type | Default | Description | | --- | --- | --- | --- | | subject | Subject | {} | Current user snapshot | | permissions | string[] | — | Legacy shortcut; merged into subject permissions | | defaultMode | 'hidden' \| 'disable' \| 'readonly' | 'hidden' | Default denied behavior for <Can /> | | roleDefinitions | RoleDefinition[] | — | Role inheritance + role-granted permissions | | permissionMatcher | (required, available) => boolean | wildcard matcher | Custom matching strategy | | permissionLoader | (subject) => Promise<PermissionResponse> | — | Load permissions/roles from backend | | resolver | (permission, subject) => Promise<boolean> | — | Per-permission remote check | | cacheTime | number (ms) | 0 | Cache TTL for loader/resolver results | | debug | boolean | false | Log permission decisions to the console | | children | ReactNode | — | App tree |

Static example

<PermissionProvider
  subject={{
    id: '42',
    roles: ['editor'],
    permissions: ['posts.read', 'posts.write'],
  }}
  defaultMode="hidden"
>
  <App />
</PermissionProvider>

Legacy permissions prop

Still supported for simple apps:

<PermissionProvider permissions={['posts.read', 'posts.write']}>
  <App />
</PermissionProvider>

Prefer subject for production systems — it carries id, roles, and attributes needed for caching and future ABAC.

Async backend loading

<PermissionProvider
  subject={{ id: currentUser.id }}
  permissionLoader={async (subject) => {
    const data = await api.getPermissions(subject.id);
    return {
      permissions: data.permissions,
      roles: data.roles,
    };
  }}
  cacheTime={5 * 60 * 1000}
>
  <App />
</PermissionProvider>

While loading, usePermission().status is 'loading' (or 'idle' before the first load starts). Use <Can loading={...} /> to avoid unauthorized flashes.

Remote resolver

Use when some checks must hit the server even if local grants miss:

<PermissionProvider
  subject={user}
  permissions={user.permissions}
  resolver={async (permission, subject) =>
    api.checkPermission(subject.id, permission)
  }
  cacheTime={60_000}
>
  <App />
</PermissionProvider>

Resolution order for canAsync(permission):

  1. Local engine allow (if already resolved)
  2. Cached resolver result
  3. In-flight dedupe
  4. Call resolver

6. Subject Model

Shape

type Subject = {
  id?: string | number;
  roles?: readonly string[];
  permissions?: readonly string[];
  attributes?: Readonly<Record<string, unknown>>;
};

Why a subject exists

Authorization is always evaluated against someone.

Bundling identity fields into one object:

  • matches how backends usually return users
  • gives the cache a stable key (id)
  • keeps roles and permissions together
  • leaves room for attributes (department, tenant, plan) without changing the React API

Why this is future-proof

Today the engine primarily uses permissions and roles.
attributes is already on the subject so ABAC conditions can attach later without renaming the model.

The core layer (src/core) does not import React. The same subject/engine design is intended to move into @abolfazl-khalaj/permission-core and be reused by Vue/Next adapters.

Mapping a backend user to Subject

Backend response:

{
  "id": 10,
  "role": "admin",
  "permissions": ["users.*", "billing.view"],
  "meta": { "department": "finance" }
}

Transform:

import type { Subject } from '@abolfazl-khalaj/permission-react';

const subject: Subject = {
  id: response.id,
  roles: [response.role], // normalize singular → array
  permissions: response.permissions,
  attributes: response.meta,
};

Role definitions (inheritance)

<PermissionProvider
  subject={{ id: '1', roles: ['admin'], permissions: [] }}
  roleDefinitions={[
    { name: 'manager', permissions: ['reports.view'] },
    {
      name: 'admin',
      inherits: ['manager'],
      permissions: ['users.*'],
    },
  ]}
>
  <App />
</PermissionProvider>

| Check | Result | Why | | --- | --- | --- | | hasRole('admin') | true | Direct | | hasRole('manager') | true | Inherited | | hasInheritedRole('manager') | true | Via inheritance only | | can('reports.view') | true | From manager role permissions | | can('users.create') | true | Wildcard from admin |


7. usePermission

Imperative authorization API. Must be called under PermissionProvider.

const {
  // subject snapshot
  subject,
  permissions,
  roles,

  // sync checks
  can,
  cannot,
  not,
  canAny,
  canAll,
  hasNone,
  check,
  explain,

  // roles
  hasRole,
  hasAnyRole,
  hasAllRoles,
  hasInheritedRole,

  // async / lifecycle
  canAsync,
  status,
  loading,
  error,
  reloadPermissions,
  clearPermissionCache,
} = usePermission();

can(permission, strategy?)

Returns whether the subject is allowed the permission(s).

can('user.create');
can(['user.create', 'user.delete'], 'all'); // every permission
can(['user.create', 'user.delete'], 'any'); // at least one

| Parameter | Type | Default | Meaning | | --- | --- | --- | --- | | permission | string \| string[] | — | Required permission(s) | | strategy | 'all' \| 'any' | 'all' | How arrays are evaluated |

Returns: boolean
Use case: Show/hide a feature in imperative code.

Wildcards are supported by default:

// subject.permissions = ['users.*']
can('users.create'); // → true
can('users.delete'); // → true
can('billing.edit'); // → false

cannot / not

Inverse of can.

if (cannot('billing.edit')) {
  showUpgradeBanner();
}

if (not('billing.edit')) {
  showUpgradeBanner();
}

not is an engine-oriented alias of cannot.


canAny / canAll / hasNone

canAny(['user.create', 'user.update']); // at least one
canAll(['user.create', 'user.delete']); // every
hasNone(['billing.edit', 'billing.delete']); // none granted

check(permission, strategy?)

Returns a structured sync result:

const result = check(['user.create', 'user.delete']);
// {
//   allowed: false,
//   missing: ['user.delete']
// }

explain(permission)

Returns a decision object (useful for debugging):

explain('users.create');
// {
//   allowed: true,
//   permission: 'users.create',
//   reason: 'permission_match',
//   source: 'users.*'
// }

explain('billing.edit');
// {
//   allowed: false,
//   permission: 'billing.edit',
//   reason: 'permission_missing'
// }

Reasons include: permission_match, permission_missing, role_missing, condition_failed, denied.

Enable console logging with debug on the provider.


Role helpers

hasRole('admin');
hasAnyRole(['admin', 'editor']);
hasAllRoles(['editor', 'member']);
hasInheritedRole('manager'); // true only via inheritance, not direct assignment

Async lifecycle

status;  // 'idle' | 'loading' | 'resolved' | 'error'
loading; // boolean shortcut for status === 'loading'
error;   // Error | null

await canAsync('invoice.delete');
await reloadPermissions();
clearPermissionCache();

| API | Behavior | | --- | --- | | canAsync | Local allow first; otherwise resolver/loader path with cache | | reloadPermissions | Invalidate cache for subject and reload via permissionLoader | | clearPermissionCache | Drop cached loader/resolver entries for current subject |


8. Can Component

Declarative authorization keeps JSX readable and avoids repeating hooks in every leaf component.

Basic usage

<Can permission="user.delete">
  <button type="button">Delete</button>
</Can>

Expected: If denied and mode is hidden (default), children are not rendered.

Why a component?

  • Colocates authorization with UI
  • Standardizes denied / loading / error presentation
  • Works well in design systems (shared fallbacks)
  • Supports render props for advanced UI states

fallback

<Can permission="user.delete" fallback={<NoAccess />}>
  <DeleteButton />
</Can>

Rendered when the check fails and mode is hidden.

loading / errorFallback

<Can
  permission="user.delete"
  loading={<Skeleton />}
  errorFallback={<PermissionError />}
  fallback={<NoAccess />}
>
  <DeleteButton />
</Can>

| State | Rendered | | --- | --- | | provider loading / idle | loading (or null if omitted — avoids unauthorized flash) | | provider error | errorFallback (falls back to fallback) | | allowed | children | | denied + hidden | fallback | | denied + disable / readonly | hardened children |

mode

{/* Hidden (default): remove from UI */}
<Can permission="invoice.edit" mode="hidden" fallback={<Locked />}>
  <EditInvoice />
</Can>

{/* Disable: visible but non-interactive */}
<Can permission="invoice.edit" mode="disable">
  <button type="button">Edit</button>
</Can>

{/* Readonly: visible, read-only semantics (aria + blocked submit/click) */}
<Can permission="invoice.edit" mode="readonly">
  <InvoiceForm />
</Can>

| Mode | User experience | | --- | --- | | hidden | Element is not shown | | disable | Element is shown but interaction is blocked (disabled / aria-disabled) | | readonly | Element is shown as read-only (aria-readonly, blocked submit/click) |

defaultMode on the provider sets the default when mode is omitted.

Render functions

<Can
  permission="user.create"
  fallback={({ allowed, mode }) => <NoAccess mode={mode} />}
>
  {({ allowed }) => (allowed ? <CreateButton /> : null)}
</Can>

Strategy for multiple permissions

<Can permission={['user.create', 'user.invite']} strategy="any">
  <InvitePanel />
</Can>

9. Route Protection

PermissionRoute gates page-level content. It is router-agnostic (works with React Router, Next.js pages, or plain composition).

<PermissionRoute
  permission="dashboard.view"
  loading={<PageLoader />}
  fallback={<Forbidden />}
  redirect="/403"
>
  <DashboardPage />
</PermissionRoute>

Props

| Prop | Description | | --- | --- | | permission | Required permission(s) | | strategy | 'all' (default) or 'any' for arrays | | loading | Shown while provider/async check is pending | | fallback | Shown when unauthorized | | redirect / redirectTo | Optional browser redirect target when denied |

Behavior

  1. While permissions are loading → render loading
  2. If sync can(...) allows → render children
  3. Otherwise try canAsync(...) (resolver / delayed grants)
  4. If still denied → render fallback, and if redirect/redirectTo is set, navigate via window.location.assign

Admin section example:

<PermissionRoute permission="admin.access" redirect="/unauthorized">
  <AdminLayout />
</PermissionRoute>

10. Real Enterprise Example

Imagine a SaaS dashboard with permissions:

| Permission | Typical use | | --- | --- | | dashboard.view | Open the main dashboard | | user.create | Create users / users nav | | user.delete | Destructive user actions | | invoice.approve | Finance workflow |

import {
  PermissionProvider,
  PermissionRoute,
  Can,
  usePermission,
} from '@abolfazl-khalaj/permission-react';

const subject = {
  id: session.user.id,
  roles: session.user.roles,
  permissions: session.user.permissions,
};

export function AppShell() {
  return (
    <PermissionProvider
      subject={subject}
      roleDefinitions={ROLE_CATALOG}
      defaultMode="hidden"
      debug={import.meta.env.DEV}
    >
      <Shell />
    </PermissionProvider>
  );
}

function Shell() {
  const { can, hasRole } = usePermission();

  return (
    <div className="layout">
      <aside>
        {/* Sidebar visibility */}
        <Can permission="dashboard.view">
          <NavLink to="/dashboard">Dashboard</NavLink>
        </Can>

        <Can permission="user.create">
          <NavLink to="/users">Users</NavLink>
        </Can>

        {hasRole('finance') ? (
          <NavLink to="/invoices">Invoices</NavLink>
        ) : null}
      </aside>

      <main>
        {/* Route protection */}
        <PermissionRoute permission="dashboard.view" fallback={<NoAccess />}>
          <DashboardPage />
        </PermissionRoute>
      </main>
    </div>
  );
}

function UsersToolbar() {
  return (
    <div>
      {/* Button protection */}
      <Can permission="user.create">
        <button type="button">Create user</button>
      </Can>

      <Can permission="user.delete" mode="disable" fallback={null}>
        <button type="button">Delete user</button>
      </Can>
    </div>
  );
}

function InvoiceActions({ invoice }: { invoice: { id: string } }) {
  const { can } = usePermission();

  return (
    <Can permission="invoice.approve" fallback={<span>Awaiting permission</span>}>
      <button type="button" onClick={() => approve(invoice.id)}>
        Approve
      </button>
      {/* Feature visibility */}
      {can('invoice.approve') ? <AuditHint /> : null}
    </Can>
  );
}

This pattern keeps:

| Concern | Approach | | --- | --- | | Sidebar | <Can /> / hasRole | | Buttons | Declarative <Can /> | | Routes | <PermissionRoute /> | | Business checks | Out of random utilities |


11. Architecture

High-level data flow:

┌─────────────────────────────────────────────┐
│              Application UI                 │
│         (pages, buttons, menus)             │
└─────────────────────┬───────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────┐
│           PermissionProvider                │
│     React adapter · context · lifecycle     │
└─────────────────────┬───────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────┐
│      Permission Store / Engine              │
│           (src/core · no React)             │
│                                             │
│   ┌───────────┐  ┌──────────┐  ┌─────────┐  │
│   │  Matchers │  │  Roles   │  │ Decisions│ │
│   │ exact + * │  │ inherit  │  │ explain │  │
│   └───────────┘  └──────────┘  └─────────┘  │
│                                             │
│   ┌─────────────────────────────────────┐   │
│   │ Async: loader · resolver · cache    │   │
│   └─────────────────────────────────────┘   │
└─────────────────────┬───────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────┐
│           Permission Decision               │
│   can() · <Can /> · <PermissionRoute />     │
└─────────────────────────────────────────────┘

Why the engine is separated

  • UI libraries should not own authorization rules
  • The same core can power Vue / Next / Angular adapters later
  • Unit tests can target pure functions without rendering React
  • Bundle stays focused: React layer is a thin wrapper

Multi-framework direction

@abolfazl-khalaj/permission-core   ← planned extraction of src/core
         ▲
         │
@abolfazl-khalaj/permission-react  ← this package (ships today)
@abolfazl-khalaj/permission-vue    ← planned
@abolfazl-khalaj/permission-next   ← planned

Today, core code ships inside the React package but lives in an isolated src/core tree with zero React imports.


12. Best Practices

Do

| Practice | Why | | --- | --- | | Load permissions from backend / token into a Subject | Single source of truth | | Use namespaced strings (invoice.approve, users.*) | Readable, scalable catalogs | | Put PermissionProvider near the top of the authenticated tree | One context for the app | | Prefer <Can /> for UI, can() for logic | Clear separation | | Use loading UI with permissionLoader | Prevents unauthorized flashes | | Use explain / debug while developing rules | Faster diagnosis | | Version roleDefinitions with your backend | Avoid drift |

Don’t

| Anti-pattern | Why | | --- | --- | | Mix auth redirects with authz in random components | Hard to reason about | | Hardcode the same permission in 20 files | Drift and typos | | Re-implement matching outside the engine | Divergent rules | | Assume permissions are ready before status === 'resolved' | Race conditions | | Depend on internal src/core paths | Not a public contract |

Suggested constants module:

export const Permissions = {
  UserCreate: 'user.create',
  UserDelete: 'user.delete',
  InvoiceApprove: 'invoice.approve',
} as const;

13. Security & Limitations

This package is for client-side / UI authorization only.
It does not replace backend authorization, API authorization, or server-side policy enforcement.

Treat every permission check in the browser as a UX and presentation control:

| Layer | Responsibility | | --- | --- | | This library | Hide, disable, or gate UI based on known grants | | Your backend | Enforce the same rules on every sensitive operation |

Security principles baked into the engine

| Principle | Behavior | | --- | --- | | Fail closed | Unknown / missing permission → deny | | Empty requirements | "" never authorizes | | Loading | While status is idle / loading, <Can /> does not render privileged children | | Subject input | Caller mutations of role/permission arrays do not affect a normalized snapshot |

What this library will not do

  • Encrypt or validate tokens
  • Prevent forged client permission arrays
  • Guarantee that a user cannot call your API without a grant
  • Replace CSRF, authn, rate limiting, or tenancy checks

Always re-check authorization on the server.


14. Migration Guide

Before

function DeleteButton({ user }: { user: { permissions: string[] } }) {
  if (!user.permissions.includes('user.delete')) {
    return null;
  }
  return <button type="button">Delete</button>;
}

Problems: prop drilling, duplicated strings, no loading/disabled modes, no wildcards/roles.

After

// root
<PermissionProvider subject={mapUserToSubject(user)}>
  <App />
</PermissionProvider>

// leaf
<Can permission="user.delete">
  <button type="button">Delete</button>
</Can>

Or imperative:

const { can } = usePermission();
return can('user.delete') ? <DeleteButton /> : null;

Migration steps:

  1. Map your auth user → Subject
  2. Wrap the authenticated tree with PermissionProvider
  3. Replace includes checks with can / <Can />
  4. Optionally introduce permissionLoader if permissions arrive asynchronously

15. Troubleshooting

usePermission must be used within a PermissionProvider

Cause: Hook/component rendered outside the provider.

Fix: Wrap the authenticated subtree (or the whole app after login) with PermissionProvider.


Permission always denied

Checklist:

  • Is status still 'loading'? Wait or show loading UI.
  • Does the subject actually include the permission (or a matching wildcard)?
  • Are permissions coming from permissionLoader but the loader failed? Check error.
  • Did you pass permissions={...} and an empty subject.permissions unexpectedly?
  • For roles: did you configure roleDefinitions if you rely on inheritance?
console.log(usePermission().subject);
console.log(usePermission().explain('user.delete'));

Wrong subject shape

roles and permissions must be arrays. If the backend sends role: "admin", convert it:

roles: Array.isArray(user.roles) ? user.roles : [user.role]

TypeScript: permission strings

Permission type is string. For stricter apps, wrap your own union:

type AppPermission = 'user.create' | 'user.delete' | 'invoice.approve';
can('user.create' as AppPermission);

Redirect does nothing in tests / SSR

PermissionRoute uses window.location.assign when redirect / redirectTo is set. In non-browser environments, provide fallback UI instead of relying on navigation.


16. API Reference

Runtime exports

| API | Kind | Purpose | Example | | --- | --- | --- | --- | | PermissionProvider | Component | Provide subject + engine/store | <PermissionProvider subject={user}> | | usePermission | Hook | Imperative authorization API | const { can } = usePermission() | | Can | Component | Declarative UI gate | <Can permission="user.delete"> | | PermissionRoute | Component | Page/section gate | <PermissionRoute permission="admin.access"> |

Common types

| Type | Purpose | | --- | --- | | Subject | User snapshot for evaluation | | Permission / PermissionList / PermissionInput | Permission identifiers | | PermissionMode | hidden | disable | readonly | | PermissionStatus | idle | loading | resolved | error | | RoleDefinition | Role inheritance + role permissions | | PermissionDecision | Result of explain() | | PermissionLoader / PermissionResolver / PermissionResponse | Async integration | | PermissionRuntimeApi | Return type of usePermission() | | CanProps / PermissionProviderProps / PermissionRouteProps | Component props | | PermissionRule / PermissionCondition / PermissionPolicy / PermissionQuery | Future ABAC surface (types only) |

Internal modules under src/core are not public API.


17. Roadmap

| Phase | Focus | Status | | --- | --- | --- | | 1 | React foundation: Provider, hooks, Can, routes, subject, engine | Done | | 2 | Advanced rules: wildcards, role hierarchy, explain/debug, operators | Done | | 3 | Async system: loader, resolver, cache, loading/error UI | Done | | 4 | ABAC evaluation + policy engine | Planned | | 5 | Extract @abolfazl-khalaj/permission-core | Planned | | 6 | Framework adapters (Next / Vue / Angular) | Planned |


License

MIT © abolfazl-khalaj