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

you-cant

v1.0.2

Published

A lightweight TypeScript policy engine for composing authorization rules with clear, reusable primitives.

Readme

you-cant

A lightweight TypeScript policy engine for composing authorization rules with clear, reusable primitives.

Installation

npm install you-cant

Overview

you-cant helps you define authorization rules as composable policies instead of scattered conditionals. It supports:

  • Permission checks
  • Predicate-based conditions
  • Logical composition with all, any, and not
  • Named policies for clearer failure messages
  • Boolean, detailed, throwing, and Result-based authorization flows

It is intentionally small. If you prefer not to add another dependency, you can also copy the library into your codebase and adapt it to your needs.

Advantages over traditional checks

With traditional authorization, rules often live inline inside handlers or services:

function canUpdateTodo(user: User, todo: Todo): boolean {
  if (user.permissions.has('admin.update_todo')) return true

  if (
    user.permissions.has('tenant.update_todo') &&
    user.tenantId === todo.tenantId &&
    todo.visibility === 'team'
  ) {
    return true
  }

  if (
    user.permissions.has('member.update_todo') &&
    user.tenantId === todo.tenantId &&
    todo.visibility === 'private' &&
    todo.createdByUserId === user.id
  ) {
    return true
  }

  return false
}

That works, but as rules grow it becomes harder to:

  • Reuse the same logic across multiple endpoints
  • Explain why access was denied
  • Review authorization separately from business logic
  • Keep checks consistent everywhere they are needed
  • Read the intent quickly when the logic is buried in nested if statements

With you-cant, the same logic becomes a reusable policy that can be evaluated anywhere:

const UpdateTodoPolicy = any<TodoPermissions, UpdateTodoContext>(
  named('admin can update todos in any tenant', permission('admin.update_todo')),
  named(
    'tenant users can update team todos in their own tenant',
    all(
      permission('tenant.update_todo'),
      predicate('todo belongs to actor tenant', (ctx) => ctx.actorTenantId === ctx.todo.tenantId),
      predicate('todo is team visible', (ctx) => ctx.todo.visibility === 'team')
    )
  ),
  named(
    'members can update their own private todos in their own tenant',
    all(
      permission('member.update_todo'),
      predicate('todo belongs to actor tenant', (ctx) => ctx.actorTenantId === ctx.todo.tenantId),
      predicate('todo is private', (ctx) => ctx.todo.visibility === 'private'),
      predicate('todo was created by actor', (ctx) => ctx.todo.createdByUserId === ctx.actorUserId)
    )
  )
)

The library approach separates policy definition from application flow, makes the rules easier to reuse, and gives you more consistent authorization behavior across the codebase. It is also easier to read because the policy names and predicates describe intent directly, instead of forcing the reader to interpret nested if statements that do not say much on their own.

Future scope

A possible future direction is policy serialization. This is not properly supported today because predicates are arbitrary functions and cannot be serialized in a reliable general-purpose way.

That said, serialization could work well for policy shapes built from boolean permission structures, such as permission, all, any, and not, where the rules can be represented as plain data instead of executable code.

Comparison with CASL, Casbin, and other RBAC libraries

Libraries such as CASL, Casbin, and more complete RBAC or ABAC systems are often a better fit when you need a broader authorization platform with features like policy storage, adapters, cross-service enforcement, or standardized models.

you-cant takes a different approach. It is useful when you want:

  • A very small library you can understand quickly
  • Policies written directly in TypeScript
  • Full control over your authorization logic without adopting a larger framework
  • Simple composition of permissions and predicates inside application code
  • Something small enough that you could even copy into your own codebase

Compared with those larger tools, the tradeoff is that you-cant is intentionally minimal. It does not try to be a full authorization system, a centralized policy engine, or a persistence-backed RBAC platform. It is closer to a tiny policy composition library for code-first authorization.

Usage

import {
  all,
  any,
  authorize,
  authorizeDetailed,
  named,
  permission,
  predicate,
  type PolicyContext,
} from 'you-cant'

type Todo = {
  tenantId: string
  createdByUserId: string
  visibility: 'private' | 'team'
}

type TodoPermissions =
  | 'admin.create_todo'
  | 'tenant.create_todo'
  | 'member.create_todo'
  | 'admin.update_todo'
  | 'tenant.update_todo'
  | 'member.update_todo'

type CreateTodoContext = {
  actorTenantId?: string
  actorUserId: string
  todoTenantId: string
  visibility: 'private' | 'team'
}

const CreateTodoPolicy = any<TodoPermissions, CreateTodoContext>(
  named(
    'admin can create todos in any tenant',
    permission('admin.create_todo')
  ),
  named(
    'tenant users can create todos in their own tenant',
    all(
      permission('tenant.create_todo'),
      predicate(
        'todo belongs to actor tenant',
        (ctx) => !!ctx.actorTenantId && ctx.actorTenantId === ctx.todoTenantId
      )
    )
  ),
  named(
    'members can create private todos in their own tenant',
    all(
      permission('member.create_todo'),
      predicate('todo belongs to actor tenant', (ctx) => !!ctx.actorTenantId && ctx.actorTenantId === ctx.todoTenantId),
      predicate('visibility is private', (ctx) => ctx.visibility === 'private'),
    )
  )
)

type UpdateTodoContext = {
  actorTenantId?: string
  actorUserId: string
  todo: Todo
}

const UpdateTodoPolicy = any<TodoPermissions, UpdateTodoContext>(
  named(
    'admin can update todos in any tenant',
    permission('admin.update_todo')
  ),
  named(
    'tenant users can update team todos in their own tenant',
    all(
      permission('tenant.update_todo'),
      predicate(
        'todo belongs to actor tenant',
        (ctx) => !!ctx.actorTenantId && ctx.actorTenantId === ctx.todo.tenantId
      ),
      predicate('todo is team visible', (ctx) => ctx.todo.visibility === 'team')
    )
  ),
  named(
    'members can update their own private todos in their own tenant',
    all(
      permission('member.update_todo'),
      predicate(
        'todo belongs to actor tenant',
        (ctx) => !!ctx.actorTenantId && ctx.actorTenantId === ctx.todo.tenantId
      ),
      predicate('todo is private', (ctx) => ctx.todo.visibility === 'private'),
      predicate('todo was created by actor', (ctx) => ctx.todo.createdByUserId === ctx.actorUserId)
    )
  )
)

const createAllowedContext: PolicyContext<TodoPermissions, CreateTodoContext> = {
  permissions: new Set(['tenant.create_todo']),
  actorTenantId: 'tenant_123',
  actorUserId: 'user_1',
  todoTenantId: 'tenant_123',
  visibility: 'team',
}

const createDeniedContext: PolicyContext<TodoPermissions, CreateTodoContext> = {
  permissions: new Set(['member.create_todo']),
  actorTenantId: 'tenant_123',
  actorUserId: 'user_2',
  todoTenantId: 'tenant_123',
  visibility: 'team',
}

const updateAllowedContext: PolicyContext<TodoPermissions, UpdateTodoContext> = {
  permissions: new Set(['member.update_todo']),
  actorTenantId: 'tenant_123',
  actorUserId: 'user_7',
  todo: {
    tenantId: 'tenant_123',
    createdByUserId: 'user_7',
    visibility: 'private',
  },
}

const createAllowed = authorize(CreateTodoPolicy, createAllowedContext)
// true

const createDenied = authorizeDetailed(CreateTodoPolicy, createDeniedContext)
// { ok: false, reason: '...' }

const updateAllowed = authorize(UpdateTodoPolicy, updateAllowedContext)
// true

API

Policy builders

  • permission(name, label?)
  • predicate(label, check)
  • all(...children)
  • any(...children)
  • not(child)
  • named(label, policy)
  • must(requiredPermissions, ...extraPolicies)
  • and(...permissions)
  • or(...permissions)

Authorization helpers

  • authorize(policy, context) returns boolean
  • authorizeDetailed(policy, context) returns { ok: true } | { ok: false; reason: string }
  • authorizeWithResult(policy, context) returns Result<void, string>
  • assertAuthorized(policy, context, message?) throws when authorization fails

Result helper

The package also exports a small Result utility with:

  • Result.ok(value)
  • Result.fail(error)
  • map
  • mapError
  • andThen
  • unwrapOrThrow
  • safelyUnwrap

Build

npm run build

License

MIT