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

@vortos/permissions

v1.0.1

Published

React permissions provider, hooks, and components for Vortos applications

Readme

@vortos/permissions

React permissions provider, hooks, and components for Vortos applications.

The backend decides what the current user can do. This package keeps the frontend permission state local, observable, refreshable, and easy to consume from React components.

Backend decides.
Frontend remembers.
Components ask locally.
Backend still enforces.

Install

npm install @vortos/permissions

React is a peer dependency:

react >= 18

Basic Setup

Wrap your app once near the router:

import { PermissionsProvider } from '@vortos/permissions';

export function App() {
  return (
    <PermissionsProvider endpoint="/api/me/permissions">
      <Router />
    </PermissionsProvider>
  );
}

The endpoint can be any route. /api/me/permissions is only the default Vortos convention.

<PermissionsProvider endpoint="/internal/session/permissions">
  <App />
</PermissionsProvider>

Response Contract

Minimum response:

{
  "permissions": ["ROLE_ADMIN", "athletes.update.any"]
}

Enterprise response with metadata:

{
  "permissions": ["athletes.update.own"],
  "roles": ["ROLE_COACH"],
  "scopes": {
    "federationId": "fed_123",
    "teamIds": ["team_7", "team_9"]
  },
  "version": "perm_2026_05_04_001"
}

permissions drives UI checks. roles, scopes, and version are available through the stateful API for debugging, audit, tenant-aware UI, and observability.

Simple Hooks

import {
  usePermission,
  usePermissions,
  useAnyPermission,
  useAllPermissions,
} from '@vortos/permissions';

const canEdit = usePermission('athletes.update.own');
const permissions = usePermissions();
const isPrivileged = useAnyPermission('ROLE_ADMIN', 'ROLE_SUPER_ADMIN');
const canSeeAnalytics = useAllPermissions('reports.read.any', 'analytics.view.any');

These hooks are intentionally simple and return only booleans or arrays.

Stateful Hooks

Use usePermissionsState() when a screen needs loading, stale, refresh, or error state:

import { usePermissionsState } from '@vortos/permissions';

function PermissionPanel() {
  const {
    permissions,
    roles,
    scopes,
    version,
    loading,
    refreshing,
    stale,
    error,
    refetch,
    has,
    hasAny,
    hasAll,
  } = usePermissionsState();

  if (loading) return <Spinner />;
  if (error) return <RetryPanel error={error} onRetry={refetch} />;

  return (
    <button disabled={refreshing} onClick={() => refetch()}>
      Refresh permissions
    </button>
  );
}

For one permission plus state:

import { usePermissionState } from '@vortos/permissions';

function DeletePostButton() {
  const { allowed, loading, error, refetch } = usePermissionState('posts.delete.any');

  if (loading) return <button disabled>Loading</button>;
  if (error) return <button onClick={() => refetch()}>Retry</button>;

  return <button disabled={!allowed}>Delete</button>;
}

Components

Use Can for conditional UI:

import { Can } from '@vortos/permissions';

<Can permission="billing.manage">
  <BillingSettings />
</Can>

With fallback:

<Can permission="billing.manage" fallback={<ReadOnlyBillingNotice />}>
  <BillingSettings />
</Can>

Disable unavailable actions instead of hiding them:

<Can
  permission="invoices.refund.any"
  fallbackMode="disable"
  deniedReason="You need the invoices.refund.any permission."
>
  <button>Refund invoice</button>
</Can>

Use RequirePermission for route guards:

import { RequirePermission } from '@vortos/permissions';

export function AdminRoute() {
  return (
    <RequirePermission
      permission="admin.dashboard.view"
      loadingFallback={<PageSpinner />}
      fallback={<Navigate to="/" replace />}
    >
      <AdminDashboard />
    </RequirePermission>
  );
}

Auth Headers

Headers are part of the provider's refetch identity. If a token changes, permissions refetch.

<PermissionsProvider
  endpoint="/api/me/permissions"
  headers={{ Authorization: `Bearer ${token}` }}
>
  <Router />
</PermissionsProvider>

Refreshing, Stale State, And Cache

<PermissionsProvider
  endpoint="/api/me/permissions"
  headers={{ Authorization: `Bearer ${token}` }}
  staleTime={30_000}
  refreshInterval={60_000}
  refetchOnWindowFocus
  retries={2}
  retryDelayMs={500}
  persist
  cacheKey={`permissions:${userId}:${tenantId}`}
>
  <Router />
</PermissionsProvider>

Use tenant-aware cache keys:

permissions:${userId}:${tenantId}

This prevents one user's cached permissions from appearing after another user logs in on the same browser.

SSR Initial Data

<PermissionsProvider
  initialPermissions={serverPermissions}
  initialRoles={serverRoles}
  initialScopes={serverScopes}
  initialVersion={serverPermissionVersion}
>
  <App />
</PermissionsProvider>

The provider still refetches on the client after mount.

Observability

<PermissionsProvider
  endpoint="/api/me/permissions"
  onError={(error) => logger.capture(error)}
  onUpdate={(state) => {
    analytics.track('permissions.updated', {
      count: state.permissions.length,
      version: state.version,
      stale: state.stale,
    });
  }}
>
  <Router />
</PermissionsProvider>

Security Boundary

Frontend permission checks are UX only. Every protected API route, command, query, and controller must still enforce authorization on the backend.