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

core-abilities.mddocs/

Core Ability Management

Core functionality for creating and managing ability instances with rule-based permissions.

Capabilities

MongoDB Ability Factory

Creates ability instances with MongoDB-style condition matching, the recommended approach for most applications.

/**
 * Creates an ability instance with MongoDB query condition matching
 * Multiple overloads support different type constraints
 */
function createMongoAbility<
  T extends AnyMongoAbility = MongoAbility
>(rules?: RawRuleOf<T>[], options?: AbilityOptionsOf<T>): T;

function createMongoAbility<
  A extends AbilityTuple = AbilityTuple,
  C extends MongoQuery = MongoQuery
>(rules?: RawRuleFrom<A, C>[], options?: AbilityOptions<A, C>): MongoAbility<A, C>;

// Options interface for ability configuration
interface AbilityOptions<A extends Abilities, C> extends RuleIndexOptions<A, C> {}

interface RuleIndexOptions<A extends Abilities, C> {
  conditionsMatcher?: ConditionsMatcher<C>;
  fieldMatcher?: FieldMatcher;
  detectSubjectType?: (subject?: any) => string;
  resolveAction?: (action: any) => string | string[];
}

// Matcher type definitions
type ConditionsMatcher<C> = (conditions: C) => MatchConditions;
type FieldMatcher = <T extends string>(fields: T[]) => MatchField<T>;
type MatchConditions<T extends {} = any> = {
  (object: T): boolean;
  ast?: any; // Condition from @ucast/mongo2js
};
type MatchField<T extends string> = (field: T) => boolean;

// MongoDB-specific types
type MongoQuery<T = AnyObject> = Record<string, any>;
type RawRuleFrom<A extends Abilities, C> = RawRule<ToAbilityTypes<A>, C>;
type ToAbilityTypes<T extends Abilities> = T extends AbilityTuple
  ? AbilityTupleType<T[0], ExtractSubjectType<T[1]>>
  : Extract<T, string>;

Usage Examples:

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

// Basic ability with rules
const ability = createMongoAbility([
  { action: 'read', subject: 'Post' },
  { action: 'update', subject: 'Post', conditions: { authorId: 'user123' } },
  { action: 'delete', subject: 'Post', inverted: true, conditions: { published: true } }
]);

// Empty ability, rules added later
const emptyAbility = createMongoAbility();
emptyAbility.update([
  { action: 'read', subject: 'Article' }
]);

// Custom options
const customAbility = createMongoAbility([], {
  detectSubjectType: (subject) => subject?.constructor?.name || subject,
  resolveAction: (action) => [action, `${action}:any`] // action aliases
});

Functional DSL

Define abilities using a functional DSL approach with can/cannot callbacks.

/**
 * Define an ability using functional DSL with can/cannot methods
 * @param define - Function that receives can/cannot callbacks to define rules
 * @param options - Configuration options for the ability instance
 * @returns Ability instance or Promise of ability (if define is async)
 */
function defineAbility<T = AnyMongoAbility>(
  define: (can: DefineRule<T>, cannot: DefineRule<T>) => void | Promise<void>,
  options?: AbilityOptionsOf<T>
): T | Promise<T>;

type DefineRule<T extends AnyAbility> = (
  action: Parameters<T['can']>[0],
  subject?: Parameters<T['can']>[1],
  conditionsOrFields?: any,
  fields?: string | string[]
) => void;

Usage Examples:

import { defineAbility } from "@casl/ability";

// Synchronous definition
const ability = defineAbility((can, cannot) => {
  can('read', 'Post');
  can('create', 'Post');
  can('update', 'Post', { authorId: 'user123' });
  cannot('delete', 'Post', { published: true });
  
  // Field-level permissions
  can('read', 'User', ['name', 'email']);
  cannot('read', 'User', ['password', 'secret']);
});

// Asynchronous definition (e.g., loading from database)
const asyncAbility = await defineAbility(async (can, cannot) => {
  const userRoles = await fetchUserRoles();
  
  if (userRoles.includes('admin')) {
    can('manage', 'all');
  } else {
    can('read', 'Post');
    can('create', 'Post');
  }
});

Pure Ability Base Class

The foundational class providing core permission checking functionality, extended by specialized ability classes.

/**
 * Base ability class providing core permission checking functionality
 */
class PureAbility<A extends Abilities = Abilities, Conditions = AnyObject> {
  /**
   * Check if action is allowed on subject
   * @param action - Action to check
   * @param subject - Subject to check against (optional)
   * @param field - Specific field to check (optional)
   * @returns true if action is allowed
   */
  can(...args: CanParameters<A>): boolean;

  /**
   * Check if action is forbidden on subject (inverse of can)
   * @param action - Action to check
   * @param subject - Subject to check against (optional)
   * @param field - Specific field to check (optional)
   * @returns true if action is forbidden
   */
  cannot(...args: CanParameters<A>): boolean;

  /**
   * Get the most relevant rule for the given action and subject
   * @param action - Action to find rule for
   * @param subject - Subject to find rule for (optional)
   * @param field - Specific field to find rule for (optional)
   * @returns The most relevant rule or null if none found
   */
  relevantRuleFor(...args: CanParameters<A>): Rule<A, Conditions> | null;

  /**
   * Update the ability's rules
   * @param rules - New array of rules to replace current rules
   * @returns this ability instance for chaining
   */
  update(rules: RawRuleFrom<A, Conditions>[]): this;

  /**
   * Detect the type of a subject object
   * @param object - Object to detect type for
   * @returns String representation of the subject type
   */
  detectSubjectType(object?: any): string;

  /**
   * Get all possible rules for an action and subject type
   * @param action - Action to get rules for
   * @param subjectType - Subject type to get rules for
   * @returns Array of potentially matching rules
   */
  possibleRulesFor(action: string, subjectType: string): Rule<A, Conditions>[];

  /**
   * Get rules that match the given criteria
   * @param action - Action to match
   * @param subjectType - Subject type to match (optional)
   * @param field - Field to match (optional)
   * @returns Array of matching rules
   */
  rulesFor(action: string, subjectType?: string, field?: string): Rule<A, Conditions>[];

  /**
   * Get all actions allowed for a subject type
   * @param subjectType - Subject type to get actions for
   * @returns Array of allowed action strings
   */
  actionsFor(subjectType: string): string[];

  /**
   * Subscribe to ability change events
   * @param event - Event name to listen for
   * @param handler - Event handler function
   * @returns Unsubscribe function
   */
  on<E extends keyof AbilityEvents<this>>(
    event: E, 
    handler: AbilityEvents<this>[E]
  ): () => void;

  /** Current rules array (readonly) */
  readonly rules: RawRuleFrom<A, Conditions>[];
}

type CanParameters<A extends Abilities> = 
  | [action: A extends readonly [infer Action, any] ? Action : A]
  | [action: A extends readonly [infer Action, any] ? Action : A, subject: A extends readonly [any, infer S] ? S : any]
  | [action: A extends readonly [infer Action, any] ? Action : A, subject: A extends readonly [any, infer S] ? S : any, field: string];

interface AbilityEvents<T> {
  update: (ability: T) => void;
  updated: (ability: T, event: { rules: any[] }) => void;
}

Usage Examples:

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

// Using via createMongoAbility (recommended)
const ability = createMongoAbility([
  { action: 'read', subject: 'Post' },
  { action: 'update', subject: 'Post', conditions: { authorId: 'user123' } }
]);

// Permission checking
console.log(ability.can('read', 'Post')); // true
console.log(ability.can('update', { __type: 'Post', authorId: 'user123' })); // true
console.log(ability.can('update', { __type: 'Post', authorId: 'other' })); // false

// Rule introspection
const relevantRule = ability.relevantRuleFor('update', { __type: 'Post', authorId: 'user123' });
console.log(relevantRule?.conditions); // { authorId: 'user123' }

const readRules = ability.rulesFor('read', 'Post');
console.log(readRules.length); // 1

const postActions = ability.actionsFor('Post');
console.log(postActions); // ['read', 'update']

// Event handling
const unsubscribe = ability.on('update', (updatedAbility) => {
  console.log('Ability rules updated:', updatedAbility.rules.length);
});

// Update rules
ability.update([
  { action: 'read', subject: 'Post' },
  { action: 'create', subject: 'Post' }
]);

unsubscribe(); // Stop listening to events

Legacy Ability Class (Deprecated)

/**
 * @deprecated Use createMongoAbility() instead
 * Legacy ability class extending PureAbility with MongoDB query matching
 */
class Ability<A extends Abilities = MongoAbility, C = MongoQuery> extends PureAbility<A, C> {
  constructor(rules?: RawRuleFrom<A, C>[], options?: AbilityOptions<A, C>);
}

Utility Functions

Subject Type Management

Utilities for working with subject types and type detection.

/**
 * Attach explicit type to subject objects (exported as setSubjectType)
 * @param type - The type to attach to the object
 * @param object - The object to attach the type to
 * @returns Object with attached type information
 */
function subject<T extends SubjectType, U extends Record<PropertyKey, any>>(
  type: T, 
  object: U
): U & ForcedSubject<T>;

/**
 * Detect the type of a subject from the object itself
 * @param object - Object to detect type for
 * @returns String representation of the subject type
 */
function detectSubjectType(object?: any): string;

/**
 * Create an action alias resolver for expanding action names
 * @param aliasMap - Map of actions to their aliases
 * @param options - Options for alias resolution
 * @returns Function that resolves action to array of action names
 */
function createAliasResolver(
  aliasMap: Record<string, string | string[]>,
  options?: { skipUnresolved?: boolean }
): (action: string) => string[];

/**
 * Ensure a value is wrapped in an array
 * @param value - Value to wrap
 * @returns Array containing the value, or the value if already an array
 */
function wrapArray<T>(value: T | T[]): T[];

Usage Examples:

import { subject, detectSubjectType, createAliasResolver, wrapArray } from "@casl/ability";

// Subject type attachment
const post = { id: 1, title: "Hello World", authorId: "user123" };
const typedPost = subject('Post', post);
console.log(detectSubjectType(typedPost)); // "Post"

// Action aliases
const resolveAction = createAliasResolver({
  manage: ['create', 'read', 'update', 'delete'],
  modify: ['create', 'update', 'delete']
});

console.log(resolveAction('manage')); // ['create', 'read', 'update', 'delete']
console.log(resolveAction('read')); // ['read'] (no alias)

// Array wrapping
console.log(wrapArray('single')); // ['single']
console.log(wrapArray(['already', 'array'])); // ['already', 'array']
console.log(wrapArray(undefined)); // [undefined]