or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-utilities.mdcore-abilities.mderror-handling.mdindex.mdpermission-checking.mdrule-building.md
tile.json

rule-building.mddocs/

Fluent Rule Building

Build complex permission rules using fluent builder pattern or functional DSL approach for flexible ability configuration.

Capabilities

AbilityBuilder Class

Fluent interface for building abilities with can/cannot methods and rule accumulation.

/**
 * Fluent interface for building abilities with rule accumulation
 */
class AbilityBuilder<T extends AnyAbility> {
  /** Array of accumulated rules */
  rules: RawRuleOf<T>[];
  
  /** Add permission rules (allow actions) */
  can: DefineRule<T>;
  
  /** Add restriction rules (deny actions) */
  cannot: DefineRule<T>;
  
  /**
   * Build the final ability instance with accumulated rules
   * @param options - Options for the ability instance
   * @returns New ability instance with all accumulated rules
   */
  build(options?: AbilityOptionsOf<T>): T;
}

/**
 * Rule definition function type used by can/cannot methods
 */
type DefineRule<T extends AnyAbility> = (
  action: Parameters<T['can']>[0],
  subject?: Parameters<T['can']>[1],
  conditionsOrFields?: any,
  fields?: string | string[]
) => void;

Usage Examples:

import { AbilityBuilder, createMongoAbility } from "@casl/ability";

// Basic builder usage
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);

// Add permission rules
can('read', 'Post');
can('create', 'Post');
can('update', 'Post', { authorId: 'user123' });
can('read', 'User', ['name', 'email']); // Field-level permissions

// Add restriction rules
cannot('delete', 'Post', { published: true });
cannot('update', 'User', { role: 'admin' });

// Build the ability
const ability = build();

console.log(ability.can('read', 'Post')); // true
console.log(ability.can('delete', { __type: 'Post', published: true })); // false

// Multiple builders for different contexts
const adminBuilder = new AbilityBuilder(createMongoAbility);
adminBuilder.can('manage', 'all'); // Admin can do everything
const adminAbility = adminBuilder.build();

const userBuilder = new AbilityBuilder(createMongoAbility);
userBuilder.can('read', 'Post');
userBuilder.can('create', 'Comment');
userBuilder.cannot('delete', 'Comment', { authorId: { $ne: 'user123' } });
const userAbility = userBuilder.build();

// Builder with custom options
const customBuilder = new AbilityBuilder(createMongoAbility);
customBuilder.can('read', 'Document');
const customAbility = customBuilder.build({
  detectSubjectType: (subject) => subject?.type || subject?.constructor?.name,
  resolveAction: (action) => action === 'manage' ? ['create', 'read', 'update', 'delete'] : action
});

Rule Definition Patterns

Common patterns for defining rules with conditions, fields, and complex logic.

/**
 * Raw rule structure for ability definitions
 */
interface RawRule<A extends Abilities, C = AnyObject> {
  /** Action or actions this rule applies to */
  action: A extends readonly [infer Action, any] ? Action : A;
  
  /** Subject or subjects this rule applies to (optional for claim-based rules) */
  subject?: A extends readonly [any, infer S] ? S : string;
  
  /** Conditions that must be met for rule to apply */
  conditions?: C;
  
  /** Field restrictions (allow only these fields) */
  fields?: string | string[];
  
  /** Whether this is a denial rule */
  inverted?: boolean;
  
  /** Reason for denial (for inverted rules) */
  reason?: string;
}

Usage Examples:

import { AbilityBuilder, createMongoAbility } from "@casl/ability";

const { can, cannot, build } = new AbilityBuilder(createMongoAbility);

// Basic permissions
can('read', 'Article');
can('create', 'Article');

// Conditional permissions with MongoDB-style queries
can('update', 'Article', { authorId: 'user123' }); // Only own articles
can('publish', 'Article', { 
  authorId: 'user123', 
  status: { $in: ['draft', 'review'] } 
}); // Complex conditions

// Field-level permissions
can('read', 'User', ['name', 'email', 'avatar']); // Only specific fields
can('update', 'User', { id: 'user123' }, ['name', 'email']); // Conditional field access

// Denial rules with reasons
cannot('delete', 'Article', { published: true }, undefined, 'Published articles cannot be deleted');
cannot('update', 'User', { role: 'admin' }, undefined, 'Admin users cannot be modified');

// Multiple actions
can(['read', 'create'], 'Comment');
cannot(['update', 'delete'], 'Comment', { locked: true });

// Multiple subjects
can('read', ['Article', 'Comment', 'User']);

// Claim-based rules (no subject)
can('login'); // Global permission
cannot('access_admin', undefined, undefined, 'Insufficient privileges');

// Complex nested conditions
can('moderate', 'Comment', {
  $or: [
    { 'article.authorId': 'user123' }, // Own article comments
    { 'assignedModerators': { $in: ['user123'] } } // Assigned moderator
  ]
});

const ability = build();

Dynamic Rule Building

Build rules dynamically based on runtime conditions like user roles or context.

/**
 * Helper type for conditional rule building
 */
type ConditionalRuleBuilder<T extends AnyAbility> = {
  when: (condition: boolean) => {
    can: DefineRule<T>;
    cannot: DefineRule<T>;
  };
};

Usage Examples:

import { AbilityBuilder, createMongoAbility } from "@casl/ability";

// Dynamic rules based on user roles
function buildUserAbility(user: { id: string; roles: string[]; department: string }) {
  const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
  
  // Base permissions for all users
  can('read', 'Profile', { userId: user.id });
  can('update', 'Profile', { userId: user.id });
  
  // Role-based permissions
  if (user.roles.includes('admin')) {
    can('manage', 'all'); // Admins can do everything
  } else {
    // Regular user permissions
    can('read', 'Article');
    can('create', 'Comment');
    can('update', 'Comment', { authorId: user.id });
    cannot('delete', 'Comment', { replies: { $gt: 0 } }, undefined, 'Cannot delete comments with replies');
  }
  
  if (user.roles.includes('editor')) {
    can(['create', 'update', 'publish'], 'Article');
    can('moderate', 'Comment');
  }
  
  if (user.roles.includes('moderator')) {
    can(['update', 'delete'], 'Comment');
    can('ban', 'User', { role: { $ne: 'admin' } });
  }
  
  // Department-based permissions
  switch (user.department) {
    case 'engineering':
      can('deploy', 'Application');
      can('access', 'ServerLogs');
      break;
    case 'marketing':
      can(['create', 'update'], 'Campaign');
      can('view', 'Analytics');
      break;
    case 'hr':
      can('manage', 'Employee');
      can('view', 'Payroll');
      break;
  }
  
  return build();
}

// Usage
const adminUser = { id: 'admin1', roles: ['admin'], department: 'engineering' };
const editorUser = { id: 'editor1', roles: ['editor'], department: 'marketing' };
const regularUser = { id: 'user1', roles: ['user'], department: 'engineering' };

const adminAbility = buildUserAbility(adminUser);
const editorAbility = buildUserAbility(editorUser);
const userAbility = buildUserAbility(regularUser);

console.log(adminAbility.can('delete', 'User')); // true (admin can manage all)
console.log(editorAbility.can('publish', 'Article')); // true (editor role)
console.log(userAbility.can('deploy', 'Application')); // true (engineering department)

// Conditional helper function
function createConditionalBuilder<T extends AnyAbility>(builder: AbilityBuilder<T>) {
  return {
    when: (condition: boolean) => ({
      can: condition ? builder.can : () => {},
      cannot: condition ? builder.cannot : () => {}
    })
  };
}

// Usage with conditional helper
function buildFeatureAbility(user: { id: string; features: string[] }) {
  const builder = new AbilityBuilder(createMongoAbility);
  const { can, cannot } = builder;
  const conditional = createConditionalBuilder(builder);
  
  // Always allowed
  can('read', 'Profile');
  
  // Feature-based permissions
  const hasFeature = (feature: string) => user.features.includes(feature);
  
  conditional.when(hasFeature('beta_editor')).can('use', 'BetaEditor');
  conditional.when(hasFeature('advanced_analytics')).can('view', 'AdvancedAnalytics');
  conditional.when(hasFeature('team_collaboration')).can('invite', 'TeamMember');
  conditional.when(!hasFeature('premium')).cannot('export', 'Report', undefined, 'Premium feature required');
  
  return builder.build();
}

Rule Priorities and Conflicts

Handle rule conflicts and priorities when multiple rules apply to the same action/subject.

/**
 * Rule priority is determined by order - later rules override earlier ones
 * More specific rules (with conditions/fields) take precedence over general rules
 */
interface RulePriorityBehavior {
  /** Later rules in the array have higher priority */
  order: 'later-wins';
  
  /** Rules with conditions have higher priority than rules without */
  specificity: 'conditions-win';
  
  /** Field-specific rules have higher priority than general rules */
  fieldSpecificity: 'field-specific-wins';
}

Usage Examples:

import { AbilityBuilder, createMongoAbility } from "@casl/ability";

const { can, cannot, build } = new AbilityBuilder(createMongoAbility);

// Rule priority by order (later wins)
can('update', 'Article'); // General rule
cannot('update', 'Article', { published: true }); // More specific rule wins

// The specific rule prevents updating published articles
// even though the general rule allows updating articles

// Field-level priority
can('read', 'User'); // Can read all user fields
cannot('read', 'User', undefined, ['password', 'socialSecurityNumber']); // Cannot read sensitive fields

// Specificity example
can('delete', 'Comment'); // General permission
cannot('delete', 'Comment', { hasReplies: true }); // Specific restriction
can('delete', 'Comment', { authorId: 'user123', hasReplies: true }); // Even more specific override

const ability = build();

// Testing priority resolution
const publishedArticle = { __type: 'Article', published: true };
const draftArticle = { __type: 'Article', published: false };

console.log(ability.can('update', draftArticle)); // true (general rule applies)
console.log(ability.can('update', publishedArticle)); // false (specific rule prevents)

const commentWithReplies = { 
  __type: 'Comment', 
  authorId: 'user123', 
  hasReplies: true 
};
const otherCommentWithReplies = { 
  __type: 'Comment', 
  authorId: 'other', 
  hasReplies: true 
};

console.log(ability.can('delete', commentWithReplies)); // true (most specific rule allows)
console.log(ability.can('delete', otherCommentWithReplies)); // false (middle rule prevents)

// Complex priority example
function buildComplexAbility() {
  const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
  
  // Base permissions
  can('read', 'Document');
  
  // Department restrictions
  cannot('read', 'Document', { department: 'hr' });
  cannot('read', 'Document', { department: 'finance' });
  
  // Role-based overrides
  can('read', 'Document', { department: 'hr', assignedUsers: { $in: ['user123'] } });
  can('read', 'Document', { department: 'finance' }, ['title', 'summary']); // Limited fields
  
  // Security level restrictions
  cannot('read', 'Document', { securityLevel: 'confidential' });
  can('read', 'Document', { 
    securityLevel: 'confidential', 
    clearanceLevel: { $gte: 3 } 
  });
  
  return build();
}