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

@zap-studio/permit

v2.0.1

Published

A type-safe, declarative, tree-shakeable authorization library for TypeScript with Standard Schema support.

Readme

@zap-studio/permit

A type-safe, declarative authorization library for TypeScript with Standard Schema support.

Full documentation: zapstudio.dev/permit

Motivation

Authorization checks written by hand, like if (user.role === "admin"), spread through a codebase over time. After a while, nobody can answer "who is allowed to delete a post?" without searching the whole app.

A framework like CASL solves the spreading problem, but it comes with its own vocabulary to learn (subject, can, cannot, rules), and its rules are not checked against your actual data shapes — you can write a rule that references a field your resource does not have, and it will only fail once that code runs.

@zap-studio/permit keeps all rules in one place, through createPolicy(...) with allow(), deny(), and when(condition) — one file answers "who can do what."

And because resources come from your Standard Schema schemas, policy types are derived straight from your real data shapes. Reference a field that does not exist, and you get an error while writing the code, not a silent undefined in production.

Installation

npm install @zap-studio/permit

You also need a schema library that implements Standard Schema, such as Zod, Valibot, or ArkType.

Features

  • Full type safety — actions, resources, and permissions are inferred from your schemas and satisfies declarations.
  • Standard Schema support via Resources — works with Zod, Valibot, ArkType, or any compatible library.
  • Declarative policies through createPolicy(...) with allow(), deny(), and when(condition).
  • Role hierarchy support via hasRole(role, hierarchy?), with inheritance resolved by collectInheritedRoles.
  • Composable conditions via and, or, and not.
  • Policy merging strategies via mergePoliciesAnd and mergePoliciesOr.
  • Structured errors with PolicyError for invalid configuration or evaluation failures.
  • Optional logging through createPolicy({ logger }) (@zap-studio/logger) — omit it and there's zero added logging overhead.
  • Tree-shakeable — policies and conditions are plain functions; unused exports are dropped by any modern bundler.

Quick Start

import { z } from "zod";
import { ConsoleLogger } from "@zap-studio/logger";
import { createPolicy, allow, deny, when } from "@zap-studio/permit";
import type { Resources, Actions } from "@zap-studio/permit";

const resources = {
  post: z.object({ id: z.string(), authorId: z.string() }),
} satisfies Resources;

const actions = {
  post: ["read", "write", "delete"],
} as const satisfies Actions<typeof resources>;

type AppContext = { user: { id: string } };

const logger = new ConsoleLogger({ minLevel: "debug" });

const policy = createPolicy<AppContext>({
  resources,
  actions,
  rules: {
    post: {
      read: allow(),
      write: when((ctx, action, resource) => ctx.user.id === resource.authorId),
      delete: deny(),
    },
  },
  logger,
});

const ctx: AppContext = { user: { id: "user-1" } };
const post = { id: "1", authorId: "user-1" };

await policy.can(ctx, "post:write", post); // true, inferred as boolean

Declarative Policies

Through createPolicy(...) with allow(), deny(), and when(condition).

rules: {
  post: {
    read: allow(),
    delete: deny(),
    write: when((ctx, action, resource) => ctx.user.id === resource.authorId),
  },
}

Role Hierarchy Support

Via hasRole(role, hierarchy?), with inheritance resolved by collectInheritedRoles.

const hierarchy = { guest: [], user: ["guest"], admin: ["user"] };

rules: {
  post: {
    read: when(hasRole("guest", hierarchy)), // admins and users inherit guest access
  },
}

Composable Conditions

Via and, or, and not.

const isOwnerOrAdmin = or(
  (ctx, action, resource) => ctx.user.id === resource.authorId,
  (ctx, action, resource) => ctx.user.role === "admin",
);

Policy Merging Strategies

Via mergePoliciesAnd and mergePoliciesOr.

const merged = mergePoliciesAnd(basePolicy, restrictivePolicy);

Standard Schema Support

Works with Zod, Valibot, ArkType, or any compatible library.

// Zod, Valibot, ArkType, or any Standard Schema-compatible library
const resources = {
  post: z.object({ id: z.string() }),
} satisfies Resources;

Structured Errors

PolicyError for invalid configuration or evaluation failures.

import { PolicyError } from "@zap-studio/permit";

try {
  const policy = createPolicy(config);
  await policy.can(ctx, "post:read", post);
} catch (error) {
  if (error instanceof PolicyError) console.error(error.message);
}

Logging

Pass a logger?: Logger from @zap-studio/logger to createPolicy(...) to observe allow/deny decisions. Omit it and only the pre-existing internal-error warnings still print, unchanged.

import { ConsoleLogger } from "@zap-studio/logger";
import { createPolicy } from "@zap-studio/permit";

const logger = new ConsoleLogger({ minLevel: "debug" });
const policy = createPolicy({ resources, actions, rules, logger });

Allow decisions log at debug, deny decisions log at info. Resource validation and policy evaluation errors log at warn through the logger when one is provided, instead of console.warn.

OpenTelemetry

@opentelemetry/api is a required peer dependency — tiny, side-effect-free, and a no-op until an app registers a real SDK, so installing it costs nothing at runtime for consumers who never set one up.

Every can(...) check gets an INTERNAL span named permit.check {resourceType}:{action}, with the decision ("allow" or "deny") set as a span attribute, plus a permit.checks counter tagged the same way. mergePoliciesAnd/mergePoliciesOr get their own span around the composite check, on top of the spans each underlying policy already produces:

npm install @opentelemetry/api
import { createPolicy } from "@zap-studio/permit";

const policy = createPolicy({ resources, actions, rules });

// If your app has registered an OpenTelemetry SDK, this call now produces a
// span attributed with the allow/deny decision. If not, it's a no-op — no
// wiring required either way.
await policy.can(ctx, "post:write", post);

Runtime Support

| Runtime | Minimum version | | ------------------ | ------------------------------------------------ | | Node.js | 18.0.0 | | Bun | 1.0.0 | | Deno | 1.42 | | Cloudflare Workers | Any current release | | Browsers | Latest evergreen (Chrome, Edge, Firefox, Safari) |

The package ships standard ESM only and uses no runtime-specific APIs. Deno 1.42 is the first release that can install packages from JSR (deno add jsr:@zap-studio/permit).

License

MIT