5.1.0 (2020-12-26)
Bug Fixes
- ability: ensure default field matcher can match fields with partial patterns inside (362f49f), closes #388
- ability: replaces getters with functions to ensure terser properly minifies them (386ecb6)
- angular: fixes sourcemap generation for the code built by ngc (7715263), closes #387 #382
- build: ensure dist is updated before (0a879f7)
- conditions: moves logic related to compare complex types to @ucast/mongo2js (9bd6a1b)
- condtions: adds interpreter for
$andparsing instruction (3166a32) - extra: makes
permittedFieldsOfto iterate from the end of array (81e6409) - package: removes
enginesection that points to npm@6 (eecd12a), closes #417 - README: removes explanation duplicated from intro guide (6315aa7)
- types: ensure
ForceSubjectgeneric parameter is preserved in resulting d.ts files (e97e5fe) - types: makes parameters of
AbilityClassoptional (e7d0815)
Code Refactoring
- extra: makes
fieldsFromoption to be mandatory forpermittedFieldsOf[skip release] (df29b0d) - ruleIndex:
detectSubjectTypeoption is now responsible only for detecting subject type from objects [skip release] (ebeaadc) - ruleIndex: removes possibility to pass subject to
rulesForandpossibleRulesFor[skip release] (b8c324d) - types: restricts which utility types are exported by library (e98618f)
Features
- builder: improves typings for AbilityBuilder [skip release] (ebd4d17), closes #379
- builder: improves typings of
AbilityBuilder['can']andAbilityBuilder['cannot']methods [skip release] (98ffbfc), closes #333 - esm: adds ESM support for latest Node.js through
exportsprop in package.json (cac2506), closes #331 - extra: adds
rulesToASTthat converts rules into @ucast AST (55fd6ee), closes #350
Performance Improvements
- ability: creates conditions and field matcher lazily (4ae7799)
- ability: replaces object for storing index with ES6 Map (d1fa117)
- events: converts LinkedItem into POJO and regular functions (6f2de73)
- events: handles event removal in space efficient way (71246e2)
- events: moves out side-effect from
LinkedItemconstructor (3657c7f) - events: utilizes LinkedList for storing event handlers (e2fd265)
- extra: replaces object with
SetinpermittedFieldsOf(a9260d1) - rule: ensures conditions/field matcher created only when we have object/field to check (110a69d)
- ruleIndex: removes subject type detection from
_buildIndexFor(13fe934) - rules: improves merging logic of rules of subject and
manage all(6f8a13a)
Reverts
- builder: reverts back
AbilityBuildergeneric parameter (aa7b45f) - extra: makes
rulesToQueryreturn an object withObjectprototype (dcb7254)
BREAKING CHANGES
-
types: types
AliasesMap,TaggedInterface,AbilityTupleType,ToAbilityTypes,AnyObjectare no longer exported by the library -
extra: makes
fieldsFromoption to be mandatory forpermittedFieldsO f. This reduces confusion around whypermittedFieldsOfreturns empty array when user can manage entity fields. So, now this logic is just explicit and clearBefore
import { defineAbility } from '@casl/ability'; import { permittedFieldsOf } from '@casl/ability/extra'; const ability = defineAbility((can) => { can('read', 'Article'); }); const fields = permittedFieldsOf(ability, 'read', 'Article'); // []
After
import { defineAbility } from '@casl/ability'; import { permittedFieldsOf } from '@casl/ability/extra'; const ability = defineAbility((can) => { can('read', 'Article'); }); const ARTICLE_FIELDS = ['id', 'title', 'description']; const fields = permittedFieldsOf(ability, 'read', 'Article', { fieldsFrom: rule => rule.fields || ARTICLE_FIELDS }); // ['id', 'title', 'description']
-
ruleIndex: string and class (or function constructor) are the only possible subject types for now.
detectSubjectTypeis now responsible only for detecting subject type from objectBefore
When providing subject type it was important to handle cases when passed in argument is a string or function. As an alternative it was possible to call built-in
detectSubjectTypewhich could catch this cases:import { Ability } from '@casl/ability'; const ability = new Ability([], { detectSubjectType(object) { if (object && typeof object === 'object') { return object.__typename; } return detectSubjectType(object); });
After
There is no need to handle subject type values in
detectSubjectTypefunction anymore. It's now handled internally:import { Ability } from '@casl/ability'; const ability = new Ability([], { detectSubjectType: object => object.__typename });
Also it's important to note that if you want it's no longer possible to use classes and strings as subject types interchangably together as it was before. Now, if you want to use classes, you should use them everywhere:
Before
import { defineAbility } from '@casl/ability'; class Post {} const ability = defineAbility((can) => { can('read', Post); can('update', 'Post'); }); ability.can('read', 'Post') // true ability.can('read', Post) // true ability.can('update', Post) // true
After
import { defineAbility } from '@casl/ability'; class Post {} const ability = defineAbility((can) => { can('read', Post); can('update', 'Post'); }); ability.can('read', 'Post') // false, 'Post' and Post are considered different now ability.can('read', Post) // true ability.can('update', Post) // false
-
ruleIndex:
rulesFor,possibleRulesFor,rulesToQuery,ruleToAST,rulesToFieldsaccepts only subject type now!Before
import { Ability } from '@casl/ability'; const ability = new Ability([ { action: 'read', subject: 'Post' } ]); class Post {} console.log(ability.rulesFor('read', new Post())); // [Rule] console.log(ability.rulesFor('read', 'Post')); // [Rule]
After
import { Ability } from '@casl/ability'; const ability = new Ability([ { action: 'read', subject: 'Post' } ]); class Post {} console.log(ability.rulesFor('read', new Post())); // throws exception console.log(ability.rulesFor('read', 'Post')); // [Rule]
Other functions and methods have the same behavior
-
builder: changes main generic parameter to be a class instead of instance and makes
defineAbilityto accept options as the 2nd argument.Before
import { AbilityBuilder, defineAbility, Ability } from '@casl/ability'; const resolveAction = (action: string) => {/* custom implementation */ }; const ability = defineAbility({ resolveAction }, (can) => can('read', 'Item')); const builder = new AbilityBuilder<Ability>(Ability);
After
import { AbilityBuilder, defineAbility, Ability } from '@casl/ability'; const resolveAction = (action: string) => {/* custom implementation */ }; const ability = defineAbility((can) => can('read', 'Item'), { resolveAction }); const builder = new AbilityBuilder(Ability); // first argument is now mandatory!
The 1st parameter to
AbilityBuilderis now madatory. This allows to infer generic parameters from it and makes AbilityType that is built to be explicit. -
builder:
canandcannotmethods ofAbilityBuildernow restricts what fields and operators can be used inside conditions (i.e.,MongoQuery). Also these methods now suggests object fields based on passed instanceBefore
import { AbilityBuilder, Ability, AbilityClass } from '@casl/ability'; interface Person { kind: 'Person' firstName: string lastName: string age: number address: { street: string city: string } } type AppAbility = Ability<['read', Person | Person['kind']]>; cons AppAbility = Ability as AbilityClass<AppAbility>; cons { can } = new AbilityBuilder(AppAbility); can('read', 'Person', { 'address.street': 'Somewhere in the world', fistName: 'John' // unintentional typo }); can('read', 'Person', ['fistName', 'lastName'], { // no intellisense for fields age: { $gt: 18 } })
After
Because provided keys in the example above doesn't exist on
Personinterface, TypeScript throws an error. So, we are safe from typos! But what about dot notation? It's also supported but in more typesafe way:import { AbilityBuilder, Ability, AbilityClass } from '@casl/ability'; interface Person { kind: 'Person' firstName: string lastName: string age: number address: { street: string city: string } } type AppAbility = Ability<['read', Person | Person['kind']]>; cons AppAbility = Ability as AbilityClass<AppAbility>; cons { can } = new AbilityBuilder(AppAbility); interface PersonQuery extends Person { 'address.street': Person['address']['street'] 'address.city': Person['address']['city'] } can<PersonQuery>('read', 'Person', { 'address.street': 'Somewhere in the world', fistName: 'John' // unintentional typo }); can<PersonQuery>('read', 'Person', ['firstName', 'lastName'], { age: { $gt: 18 } })
Intellisense and type checking for fields is also implemented! To be able to use wildcards in fields just add additional generic parameter:
can<PersonQuery, 'address.*'>('read', 'Person', ['firstName', 'address.*'], { age: { $gt: 18 } })