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

@nyalajs/permissions

v0.1.0

Published

Database-backed, enterprise-grade role & permission management for NyalaJS — Spatie laravel-permission parity (and beyond): roles, direct permissions, teams/tenant scoping, wildcard permissions, caching, super-admin bypass.

Readme

@nyalajs/permissions

Database-backed role & permission management for Nyala.js — Spatie laravel-permission parity, and beyond: roles, direct permission grants, teams (multi-tenant role scoping), wildcard permissions, always-on caching, a super-admin bypass, and guards that check live instead of trusting a JWT's claims until the next login.

Why not just @Roles()/RolesGuard?

@nyalajs/security's @Roles()/RolesGuard check role names baked into the JWT at login — revoke a role, and it's still valid until the token expires or the user logs in again. This package is fully database-backed: DBRolesGuard reads the same @Roles() decorator but checks the database on every request, so a revoked role stops working on the very next call.

Quick start

import { Injectable } from "@nyalajs/core";
import { PermissionManager } from "@nyalajs/permissions";

@Injectable()
class UsersService {
  constructor(private permissions: PermissionManager) {}

  async promote(user: User) {
    await this.permissions.assignRole(user, "editor");
  }

  async canPublish(user: User) {
    return this.permissions.can(user, "posts.publish");
  }
}

Wire it into a module:

import { Module } from "@nyalajs/core";
import { permissionsProviders } from "@nyalajs/permissions";

@Module({
  providers: [
    ...permissionsProviders({ superAdminRoles: ["super-admin"] }),
    UsersService,
  ],
})
export class AppModule {}

(This framework's @Module() only accepts imports: Type[] — concrete module classes, not a NestJS-style dynamic module object — so permissionsProviders() is a plain function returning the provider list, not a .forRoot() you import.)

Migration

Copy node_modules/@nyalajs/permissions/migrations/create_permissions_tables.ts into your app's database/migrations/ (renumbered to fit your sequence) and run nyala db:migrate. Written for Postgres, matching the nyala db:migrate CLI (currently Postgres-only).

Guards

import { UseGuards } from "@nyalajs/core";
import { AuthGuard } from "@nyalajs/security";
import { Permissions, PermissionsGuard, DBRolesGuard, RoleOrPermission, RoleOrPermissionGuard } from "@nyalajs/permissions";

@UseGuards(AuthGuard, PermissionsGuard)
@Permissions("posts.delete")
@Delete(":id")
remove() { ... }

// Drop-in replacement for @nyalajs/security's RolesGuard — same @Roles() decorator, DB-backed instead of JWT-claims-backed.
@UseGuards(AuthGuard, DBRolesGuard)
@Roles("admin")
@Get("admin-only")
adminOnly() { ... }

// Passes on either a matching ROLE or a matching PERMISSION.
@UseGuards(AuthGuard, RoleOrPermissionGuard)
@RoleOrPermission("editor", "posts.edit")
@Put(":id")
update() { ... }

AuthGuard must run first — every guard here reads context.context.metadata.get("user"), which AuthGuard populates.

API (Spatie parity)

PermissionManager — the single entry point most code should use (TypeScript has no traits, so this is a companion service standing in for Spatie's HasRoles/HasPermissions Eloquent traits):

| Method | | |---|---| | assignRole / removeRole / syncRoles | | | hasRole / hasAnyRole / hasAllRoles / hasExactRoles | | | getRoleNames | | | givePermissionTo / revokePermissionTo / syncPermissions | direct grants, bypassing roles | | can (alias for hasPermissionTo) / hasAnyPermission / hasAllPermissions | direct + via-role, wildcard-aware | | hasDirectPermission / hasAnyDirectPermission / hasAllDirectPermissions | direct grants only | | hasPermissionViaRole | role-derived only | | getDirectPermissionNames / getPermissionNamesViaRoles / getAllPermissions | |

Every method takes (model: {id}, name, options?, modelType = "User") — works on any model, not just User (Spatie's polymorphic model_has_roles/model_has_permissions).

Wildcard permissions (Spatie doesn't have this by default)

await permissions.givePermissionTo(user, "posts.*");
await permissions.can(user, "posts.create"); // true
await permissions.can(user, "posts.delete"); // true
await permissions.can(user, "comments.delete"); // false

Trailing-wildcard matching only (posts.*, not posts.*.delete) — predictable semantics over glob completeness, since this runs on every permission check.

Teams (multi-tenant role scoping)

await permissions.assignRole(user, "admin", { tenantId: acmeCorpId });
await permissions.hasRole(user, "admin", { tenantId: acmeCorpId }); // true
await permissions.hasRole(user, "admin", { tenantId: otherCorpId }); // false
await permissions.hasRole(user, "admin"); // false — global scope is distinct from any team

Pass your app's real tenant id directly — no translation needed. (Internally this is stored in a teamId column, not tenantId: @nyalajs/database auto-scopes any table with a column literally named tenantId, throwing without an active TenantContext — wrong here, since global and team-scoped roles must coexist in the same table. RoleService/PermissionService apply team filtering explicitly instead.)

Super-admin bypass

providers: [...permissionsProviders({ superAdminRoles: ["super-admin"] })]

Mirrors Laravel's Gate::before() convention — a subject with this role skips every permission/role check unconditionally, checked against the DB-backed role list (not JWT claims), so it can be revoked immediately too.

Caching

Every getAllPermissionNames() resolution (the thing every can()/hasPermissionTo() call ultimately checks) is cached via @nyalajs/cache's CacheService — always on, degrading to an in-memory store if Redis isn't configured, never a no-op. Any write (assignRole, givePermissionTo, a role's permissions changing, etc.) invalidates the relevant cache entry immediately, so there's no manual forgetCachedPermissions() step to remember.

What's NOT Included

  • No Blade-directive equivalent — this is a backend permission layer; gate your own frontend/template rendering on PermissionManager.can() results passed down as booleans.
  • No Artisan-equivalent CLI commands yet (permission:create-role, permission:show, etc.) — use PermissionManager/RoleService/PermissionService directly, or a seeder script.
  • No event system (Spatie's RoleAttachedEvent etc.) — hook into your own call sites if you need side effects on role/permission changes.