So far, we were able to configure your policies. But we can't yet check that our actions or subjects are actually matching what we declared.
Let's configure our ability types:
From ./test/demo/better-types/ability.ts
import { Ability } from '@casl/ability';
export type MyAbilities =
    | ['admin', 'ImportantData']
    | ['create' | 'read' | 'update' | 'delete', 'PublicData' ];
export type MyAbility = Ability<MyAbilities>;
Using MyAbility, we can't pass anything to can or cannot: types are constrained.
From ./test/demo/better-types/ability.typecheck.e2e-spec.ts
import { expectTypeOf } from 'expect-type';
import { MyAbility } from './ability';
it( 'ability should have correct typings', () => {
    expectTypeOf<['read', 'PublicData']>().toMatchTypeOf<Parameters<MyAbility['can']>>();
    expectTypeOf<['create', 'PublicData']>().toMatchTypeOf<Parameters<MyAbility['can']>>();
    expectTypeOf<['admin', 'ImportantData']>().toMatchTypeOf<Parameters<MyAbility['can']>>();
    expectTypeOf<['admin', 'PublicData']>().not.toMatchTypeOf<Parameters<MyAbility['can']>>();
    expectTypeOf<['read', 'ImportantData']>().not.toMatchTypeOf<Parameters<MyAbility['can']>>();
} );
That's great. This will greatly reduce our chances of typos. Moreover, your IDE might now suggest actions & subjects for you.
CaslAbilityFactoryLet's now use this type in your CaslAbilityFactory:
From src/ability-factory.service.ts
import { AbilityBuilder, PureAbility } from '@casl/ability';
import { Injectable } from '@nestjs/common';
import { CaslAbilityFactory } from '@knodes/nest-casl';
import { MyAbility } from './ability';
@Injectable()
export class AbilityFactory implements CaslAbilityFactory<MyAbility> {
    // Here, \`request\` is the express or fastify request. You might get infos from it.
    public createFromRequest( _request: unknown ): MyAbility {
        const { user } = ( _request as any );
        const abilityBuilder = new AbilityBuilder<MyAbility>( PureAbility );
        if( user?.role === 'admin' ) {
            abilityBuilder.can( 'admin', 'ImportantData' );
        }
        return abilityBuilder.build();
    }
}
You can pass your ability as a type parameter to your decorators to constraint your actions and subjects:
From src/test.controller.ts
import { Controller, Get } from '@nestjs/common';
import { Policy } from '@knodes/nest-casl';
import { MyAbility } from './ability';
@Controller()
// @ts-expect-error -- \`something\` is not a valid subject
@Policy<MyAbility>( { action: 'admin', subject: 'something' } )
// @ts-expect-error -- \`rick-roll\` is not a valid action
@Policy<MyAbility>( { action: 'rick-roll', subject: 'ImportantData' } )
// @ts-expect-error -- \`read\` is not a valid action on \`ImportantData\`
@Policy<MyAbility>( { action: 'read', subject: 'ImportantData' } )
@Policy<MyAbility>( { action: 'read', subject: 'PublicData' } )
// ...
export class TestController {
    @Get()
    public method(){
        // ...
    }
}
But the boring part here is that you have to pass your ability type to every decorator in order to constrain them. Hopefully, you can solve this:
From src/my-policies.ts
import { bindPolicyDecorators } from '@knodes/nest-casl';
import { MyAbility } from './ability';
export const MyPolicies = bindPolicyDecorators<MyAbility>( /* you can even pass some guards here ! */ );
Then, simply enjoy type contraints !
From src/test-bound.controller.ts
import { Controller, Get } from '@nestjs/common';
import { MyPolicies } from './my-policies';
@Controller()
@MyPolicies.PoliciesMask( {
    '*': { action: 'admin', subject: 'ImportantData' },
    'read': { action: 'read', subject: 'PublicData' },
    'create': { action: 'create', subject: 'PublicData' },
} )
export class TestController {
    @Get()
    @MyPolicies.Policy( { handle: ability => ability.can( 'read', 'PublicData' ) } )
    public create(){
        // ...
    }
    @Get()
    public read(){
        // ...
    }
    @Get()
    public admin(){
        // ...
    }
}
Generated using TypeDoc