@nestbolt/permissions
Events
Lifecycle events emitted by the permissions package -- role and permission creation, deletion, attachment, detachment, and cache flushes.
The package emits events throughout the lifecycle of roles and permissions. These events let you build audit trails, trigger side effects, synchronize external systems, or log access changes.
Prerequisites
Events require the @nestjs/event-emitter package to be installed and registered in your application:
npm install @nestjs/event-emitterimport { Module } from "@nestjs/common";
import { EventEmitterModule } from "@nestjs/event-emitter";
import { PermissionsModule } from "@nestbolt/permissions";
@Module({
imports: [
EventEmitterModule.forRoot(),
PermissionsModule.forRoot({
userRepository: TypeOrmPermissionUserRepository,
}),
],
})
export class AppModule {}If @nestjs/event-emitter is not installed, the package functions normally but silently skips all event emissions. No errors are thrown.
Event Constants
All event names are available via the PERMISSION_EVENTS constant:
import { PERMISSION_EVENTS } from "@nestbolt/permissions";const PERMISSION_EVENTS = {
ROLE_ATTACHED: "permissions.role-attached",
ROLE_DETACHED: "permissions.role-detached",
PERMISSION_ATTACHED: "permissions.permission-attached",
PERMISSION_DETACHED: "permissions.permission-detached",
ROLE_CREATED: "permissions.role-created",
ROLE_DELETED: "permissions.role-deleted",
PERMISSION_CREATED: "permissions.permission-created",
PERMISSION_DELETED: "permissions.permission-deleted",
CACHE_FLUSHED: "permissions.cache-flushed",
} as const;Event Payload Types
The package exports four payload interfaces:
import type {
RoleEvent,
PermissionEvent,
RoleLifecycleEvent,
PermissionLifecycleEvent,
} from "@nestbolt/permissions";RoleEvent
Used for role attachment and detachment events.
interface RoleEvent {
userId: string;
roleIds: string[];
}| Property | Type | Description |
|---|---|---|
userId | string | The ID of the user whose roles changed. |
roleIds | string[] | The IDs of the roles that were attached or detached. |
PermissionEvent
Used for permission attachment and detachment events.
interface PermissionEvent {
userId: string;
permissionIds: string[];
}| Property | Type | Description |
|---|---|---|
userId | string | The ID of the user whose permissions changed. |
permissionIds | string[] | The IDs of the permissions that were attached or detached. |
RoleLifecycleEvent
Used for role creation and deletion events.
interface RoleLifecycleEvent {
role: {
id: string;
name: string;
guardName: string;
};
}| Property | Type | Description |
|---|---|---|
role.id | string | The UUID of the role. |
role.name | string | The name of the role (e.g., "admin"). |
role.guardName | string | The guard name (e.g., "default"). |
PermissionLifecycleEvent
Used for permission creation and deletion events.
interface PermissionLifecycleEvent {
permission: {
id: string;
name: string;
guardName: string;
};
}| Property | Type | Description |
|---|---|---|
permission.id | string | The UUID of the permission. |
permission.name | string | The name of the permission (e.g., "posts.create"). |
permission.guardName | string | The guard name (e.g., "default"). |
Event Reference
permissions.role-attached
Emitted when one or more roles are assigned to a user via PermissionRegistrarService.assignRole().
| Constant | PERMISSION_EVENTS.ROLE_ATTACHED |
| Payload | RoleEvent |
| Triggered by | PermissionRegistrarService.assignRole() |
import { OnEvent } from "@nestjs/event-emitter";
import { Injectable } from "@nestjs/common";
import { PERMISSION_EVENTS } from "@nestbolt/permissions";
import type { RoleEvent } from "@nestbolt/permissions";
@Injectable()
export class RoleAttachedListener {
@OnEvent(PERMISSION_EVENTS.ROLE_ATTACHED)
handleRoleAttached(payload: RoleEvent) {
console.log(`Roles ${payload.roleIds.join(", ")} assigned to user ${payload.userId}`);
}
}permissions.role-detached
Emitted when one or more roles are removed from a user via PermissionRegistrarService.removeRole().
| Constant | PERMISSION_EVENTS.ROLE_DETACHED |
| Payload | RoleEvent |
| Triggered by | PermissionRegistrarService.removeRole() |
@OnEvent(PERMISSION_EVENTS.ROLE_DETACHED)
handleRoleDetached(payload: RoleEvent) {
console.log(`Roles ${payload.roleIds.join(", ")} removed from user ${payload.userId}`);
}permissions.permission-attached
Emitted when one or more direct permissions are granted to a user via PermissionRegistrarService.givePermissionTo().
| Constant | PERMISSION_EVENTS.PERMISSION_ATTACHED |
| Payload | PermissionEvent |
| Triggered by | PermissionRegistrarService.givePermissionTo() |
@OnEvent(PERMISSION_EVENTS.PERMISSION_ATTACHED)
handlePermissionAttached(payload: PermissionEvent) {
console.log(
`Permissions ${payload.permissionIds.join(", ")} granted to user ${payload.userId}`,
);
}permissions.permission-detached
Emitted when one or more direct permissions are revoked from a user via PermissionRegistrarService.revokePermissionTo().
| Constant | PERMISSION_EVENTS.PERMISSION_DETACHED |
| Payload | PermissionEvent |
| Triggered by | PermissionRegistrarService.revokePermissionTo() |
@OnEvent(PERMISSION_EVENTS.PERMISSION_DETACHED)
handlePermissionDetached(payload: PermissionEvent) {
console.log(
`Permissions ${payload.permissionIds.join(", ")} revoked from user ${payload.userId}`,
);
}permissions.role-created
Emitted when a new role is created via RoleService.create() or the creation path of RoleService.findOrCreate().
| Constant | PERMISSION_EVENTS.ROLE_CREATED |
| Payload | RoleLifecycleEvent |
| Triggered by | RoleService.create() |
@OnEvent(PERMISSION_EVENTS.ROLE_CREATED)
handleRoleCreated(payload: RoleLifecycleEvent) {
console.log(`Role "${payload.role.name}" created with ID ${payload.role.id}`);
}permissions.role-deleted
Emitted when a role is deleted via RoleService.delete().
| Constant | PERMISSION_EVENTS.ROLE_DELETED |
| Payload | RoleLifecycleEvent |
| Triggered by | RoleService.delete() |
@OnEvent(PERMISSION_EVENTS.ROLE_DELETED)
handleRoleDeleted(payload: RoleLifecycleEvent) {
console.log(`Role "${payload.role.name}" deleted`);
}permissions.permission-created
Emitted when a new permission is created via PermissionService.create() or the creation path of PermissionService.findOrCreate().
| Constant | PERMISSION_EVENTS.PERMISSION_CREATED |
| Payload | PermissionLifecycleEvent |
| Triggered by | PermissionService.create() |
@OnEvent(PERMISSION_EVENTS.PERMISSION_CREATED)
handlePermissionCreated(payload: PermissionLifecycleEvent) {
console.log(
`Permission "${payload.permission.name}" created for guard "${payload.permission.guardName}"`,
);
}permissions.permission-deleted
Emitted when a permission is deleted via PermissionService.delete().
| Constant | PERMISSION_EVENTS.PERMISSION_DELETED |
| Payload | PermissionLifecycleEvent |
| Triggered by | PermissionService.delete() |
@OnEvent(PERMISSION_EVENTS.PERMISSION_DELETED)
handlePermissionDeleted(payload: PermissionLifecycleEvent) {
console.log(`Permission "${payload.permission.name}" deleted`);
}permissions.cache-flushed
Emitted when the permission cache is manually flushed via PermissionRegistrarService.flushCache(). Note that internal cache flushes (triggered automatically when roles or permissions are modified) do not emit this event.
| Constant | PERMISSION_EVENTS.CACHE_FLUSHED |
| Payload | {} (empty object) |
| Triggered by | PermissionRegistrarService.flushCache() |
@OnEvent(PERMISSION_EVENTS.CACHE_FLUSHED)
handleCacheFlushed() {
console.log("Permission cache was manually flushed");
}Complete Audit Log Listener Example
Here is a full example of a listener service that logs all permission-related events for audit purposes:
import { Injectable, Logger } from "@nestjs/common";
import { OnEvent } from "@nestjs/event-emitter";
import { PERMISSION_EVENTS } from "@nestbolt/permissions";
import type {
RoleEvent,
PermissionEvent,
RoleLifecycleEvent,
PermissionLifecycleEvent,
} from "@nestbolt/permissions";
@Injectable()
export class PermissionAuditListener {
private readonly logger = new Logger(PermissionAuditListener.name);
@OnEvent(PERMISSION_EVENTS.ROLE_ATTACHED)
onRoleAttached(payload: RoleEvent) {
this.logger.log(
`User ${payload.userId} was assigned roles: ${payload.roleIds.join(", ")}`,
);
}
@OnEvent(PERMISSION_EVENTS.ROLE_DETACHED)
onRoleDetached(payload: RoleEvent) {
this.logger.log(
`User ${payload.userId} had roles removed: ${payload.roleIds.join(", ")}`,
);
}
@OnEvent(PERMISSION_EVENTS.PERMISSION_ATTACHED)
onPermissionAttached(payload: PermissionEvent) {
this.logger.log(
`User ${payload.userId} was granted permissions: ${payload.permissionIds.join(", ")}`,
);
}
@OnEvent(PERMISSION_EVENTS.PERMISSION_DETACHED)
onPermissionDetached(payload: PermissionEvent) {
this.logger.log(
`User ${payload.userId} had permissions revoked: ${payload.permissionIds.join(", ")}`,
);
}
@OnEvent(PERMISSION_EVENTS.ROLE_CREATED)
onRoleCreated(payload: RoleLifecycleEvent) {
this.logger.log(
`Role created: "${payload.role.name}" (guard: ${payload.role.guardName}, id: ${payload.role.id})`,
);
}
@OnEvent(PERMISSION_EVENTS.ROLE_DELETED)
onRoleDeleted(payload: RoleLifecycleEvent) {
this.logger.log(
`Role deleted: "${payload.role.name}" (guard: ${payload.role.guardName})`,
);
}
@OnEvent(PERMISSION_EVENTS.PERMISSION_CREATED)
onPermissionCreated(payload: PermissionLifecycleEvent) {
this.logger.log(
`Permission created: "${payload.permission.name}" (guard: ${payload.permission.guardName}, id: ${payload.permission.id})`,
);
}
@OnEvent(PERMISSION_EVENTS.PERMISSION_DELETED)
onPermissionDeleted(payload: PermissionLifecycleEvent) {
this.logger.log(
`Permission deleted: "${payload.permission.name}" (guard: ${payload.permission.guardName})`,
);
}
@OnEvent(PERMISSION_EVENTS.CACHE_FLUSHED)
onCacheFlushed() {
this.logger.log("Permission cache was manually flushed");
}
}Register the listener in a module:
import { Module } from "@nestjs/common";
import { PermissionAuditListener } from "./permission-audit.listener";
@Module({
providers: [PermissionAuditListener],
})
export class AuditModule {}Events Not Emitted by syncRoles() and syncPermissions()
The syncRoles() and syncPermissions() methods on PermissionRegistrarService perform bulk operations (detach all, then attach new). They call the PermissionUserRepository methods directly and flush the cache, but they do not emit ROLE_ATTACHED, ROLE_DETACHED, PERMISSION_ATTACHED, or PERMISSION_DETACHED events. If you need to track sync operations, consider wrapping these calls with your own event emission or using the CACHE_FLUSHED event as an indirect indicator that something changed.
Using Event Constants as String Literals
If you prefer to use string literals instead of the constant object, you can subscribe to events by their string names directly:
@OnEvent("permissions.role-attached")
handleRoleAttached(payload: RoleEvent) {
// ...
}However, using PERMISSION_EVENTS.ROLE_ATTACHED is recommended because it provides type safety and makes refactoring easier.