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

@zanzojs/react

v0.2.0

Published

React bindings for Zanzo ReBAC. O(1) permission checks via ZanzoProvider and useZanzo hook.

Readme

@zanzojs/react

npm version React Compatible

React bindings for ZanzoJS. O(1) permission checks on the frontend with zero network requests after hydration.

How it works

The server compiles a flat permission map (snapshot) once per user. The frontend receives it, hydrates it into a ZanzoProvider, and evaluates every permission check as a simple Map lookup — no graphs, no network, no re-renders.

Server: engine.load() → createZanzoSnapshot() → JSON response
Client: ZanzoProvider → useZanzo().can() → O(1) boolean

Installation

pnpm add @zanzojs/core @zanzojs/react

Step-by-Step Guide

1. Generate the snapshot on the server

On login or on each page load, compile the snapshot for the authenticated user. Always create a fresh engine per request.

import { ZanzoEngine, createZanzoSnapshot } from '@zanzojs/core';
import { schema } from './zanzo.config';
import { db, zanzoTuples } from './db';
import { eq } from 'drizzle-orm';

export async function getUserSnapshot(userId: string) {
  const actor = `User:${userId}`;

  // 1. Load this user's direct assignment tuples
  const userTuples = await db.select()
    .from(zanzoTuples)
    .where(eq(zanzoTuples.subject, actor));

  // 2. Load structural tuples (the skeleton of your graph)
  // This allows the engine to walk paths like "Document -> folder -> Folder"
  const structuralTuples = await db.select()
    .from(zanzoTuples)
    .where(inArray(zanzoTuples.relation, ['folder', 'workspace', 'parent']));

  // Fresh engine per request — never reuse a shared instance across requests
  const requestEngine = new ZanzoEngine(schema);
  requestEngine.load([...userTuples, ...structuralTuples]);

  return createZanzoSnapshot(requestEngine, actor);
}

[!IMPORTANT] Why structuralTuples? If your schema uses nested paths (e.g. folder.admin), the engine needs the relationship between a Document and its Folder to evaluate the path. If you only load User:alice -> editor -> Folder:1, the engine won't know which documents belong to that folder unless you also load the Document:A -> folder -> Folder:1 tuples.

Never reuse the engine across requests. A shared engine would accumulate tuples from multiple users. Always instantiate a new ZanzoEngine per request.

2. Wrap your app with ZanzoProvider

'use client';
import { ZanzoProvider } from '@zanzojs/react';

interface AppLayoutProps {
  children: React.ReactNode;
  snapshot: Record<string, string[]>;
}

export default function AppLayout({ children, snapshot }: AppLayoutProps) {
  return (
    <ZanzoProvider snapshot={snapshot}>
      {children}
    </ZanzoProvider>
  );
}

3. Check permissions in any client component

'use client';
import { useZanzo } from '@zanzojs/react';

export function DocumentActions({ documentId }: { documentId: string }) {
  const { can } = useZanzo();

  return (
    <div>
      {can('read', `Document:${documentId}`) && <ReadButton />}
      {can('write', `Document:${documentId}`) && <EditButton />}
      {can('delete', `Document:${documentId}`) && <DeleteButton />}
    </div>
  );
}

4. List accessible resources (O(n))

'use client';
import { useZanzo } from '@zanzojs/react';

export function DocumentList() {
  const { listAccessible } = useZanzo();

  // O(n) — iterates the snapshot. Use for rendering lists, not in tight loops.
  const docs = listAccessible('Document');

  return (
    <ul>
      {docs.map(({ object, actions }) => (
        <li key={object}>
          {object} — {actions.join(', ')}
        </li>
      ))}
    </ul>
  );
}

can() is O(1). listAccessible() is O(n). Use can() for individual checks inside render loops. Use listAccessible() to build lists of accessible resources.

Keeping the snapshot fresh

The snapshot reflects permissions at the time it was compiled. If permissions change after compilation, the client snapshot becomes stale.

Recommended strategies:

Re-fetch on critical routes — Force a fresh snapshot on sensitive pages:

// In a Next.js Server Component
const snapshot = await getUserSnapshot(userId); // always fresh

Invalidate on permission change — When granting or revoking access, invalidate the cached snapshot immediately:

await redis.del(`snapshot:${userId}`);

TTL-based revalidation — Cache the snapshot with a short TTL (e.g. 5 minutes) and revalidate in the background.

Documentation

For backend setup and database adapters, see the ZanzoJS Monorepo.