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

@shefing/abac

v0.0.4

Published

`@shefing/abac` adds a pluggable, Payload-native attribute-based access control layer.

Readme

ABAC Plugin

@shefing/abac adds a pluggable, Payload-native attribute-based access control layer.

Goals

  • Keep the implementation simple and Payload-first.
  • Compile attributes into Payload where clauses whenever possible.
  • Reuse JWT-enriched user data instead of introducing a parallel permission store.

Install

pnpm add @shefing/abac

Core contract

import type { AttributeProvider } from '@shefing/abac'

const tenantAttribute: AttributeProvider<string | null> = {
  key: 'tenant',
  fromUser: (user) => (typeof user.tenant === 'string' ? user.tenant : null),
  match: (userValue, docValue) => userValue != null && userValue === docValue,
  toWhere: (userValue) => ({ tenant: { equals: userValue } }),
  enrichJWT: (user) => ({ tenant: user.tenant ?? null }),
}

Tenant example

import { abacPlugin } from '@shefing/abac'

plugins: [
  abacPlugin({
    attributes: [
      {
        key: 'tenant',
        fromUser: (user) => user.tenant,
        match: (userValue, docValue) => userValue === docValue,
        toWhere: (userValue) => ({ tenant: { equals: userValue } }),
        enrichJWT: (user) => ({ tenant: user.tenant }),
      },
    ],
  }),
]

Then opt collections in with custom.abac:

custom: {
  abac: {
    tenant: {
      docField: 'tenant',
    },
  },
}

Geo example

geo is intentionally documented as an example for v1 rather than shipped as a built-in:

const geoAttribute = {
  key: 'geo',
  fromUser: (user) => user.region,
  match: (userRegion, docRegion) => userRegion === docRegion,
  toWhere: (userRegion) => ({ region: { equals: userRegion } }),
}

Multi-value attributes (v0.2 — coming next)

Current limitation (v1): each attribute provider resolves to a single value per user (e.g. user.tenant = 'tenant-a'). If a user belongs to multiple tenants the engine only sees the first one.

The v0.2 milestone will extend the engine and tenantAttribute to handle array-valued attributes natively:

// user.tenants = ['tenant-a', 'tenant-b']  ← multiple memberships

const multiTenantAttribute = {
  key: 'tenant',
  fromUser: (user) => Array.isArray(user.tenants) ? user.tenants : [],
  match: (userTenants, docTenant) =>
    Array.isArray(userTenants) && userTenants.includes(docTenant as string),
  // toWhere will emit { tenant: { in: ['tenant-a', 'tenant-b'] } }
  toWhere: (userTenants) => ({ tenant: { in: userTenants as string[] } }),
  enrichJWT: (user) => ({ tenants: Array.isArray(user.tenants) ? user.tenants : [] }),
}

This closes the main gap vs. Payload's official plugin-multi-tenant, which stores a tenants[] array per user. The same pattern applies to any set-valued attribute (multi-region, multi-clearance, etc.).


Notes

  • v1 keeps policy composition simple: matching providers are combined with implicit AND.
  • Built-in tenantAttribute() and roleAttribute() are exported from the package entry.
  • roleAttribute() is additive with @shefing/authorization: it preserves JWT role context and can short-circuit admins, but it does not replace RBAC collection permissions.
  • See the integration coverage in test-app/tests/e2e-plugins/abac.spec.ts once the feature is fully wired.

Roadmap

| Version | Item | Status | |---------|------|--------| | v1 | Single-value attribute providers (tenant, role, custom) | ✅ Shipped | | v1 | JWT enrichment — zero extra DB queries per request | ✅ Shipped | | v1 | GET /api/me/permissions endpoint | ✅ Shipped | | v1 | abacFilterOptions relationship field helper | ✅ Shipped | | v1 | Fail-closed safety + startup config validation | ✅ Shipped | | v0.2 | Multi-value attribute support (tenants[], { in: [...] } WHERE, array match) | ✅ Shipped | | v0.2 | readVersions / unlock action guards | ✅ Shipped | | v0.2 | pluginContext singleton → factory (test isolation) | ✅ Shipped | | v0.3 | Optional admin UI: tenant switcher component | 💡 Planned | | v0.3 | isGlobal collection mode (per-tenant globals) | 💡 Planned | | v0.3 | Per-tenant roles (tenantRoleAttribute) | 💡 Planned | | v0.3 | i18n support | 💡 Planned |