Skip to content

Commit

Permalink
feat(ability): improves typing for GetSubjectName and adds default va…
Browse files Browse the repository at this point in the history
…lues for generics

Also adds default generic values for AbilityModule in angular package

Relates to #256
  • Loading branch information
stalniy committed Feb 17, 2020
1 parent 68bd287 commit c089293
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 47 deletions.
14 changes: 7 additions & 7 deletions packages/casl-ability/src/Ability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Rule, ConditionsMatcher, FieldMatcher } from './Rule';
import { RawRule } from './RawRule';
import { wrapArray, getSubjectName, expandActions, AliasesMap } from './utils';
import { GetSubjectName, Subject, ExtractSubjectType as E, ValueOf } from './types';
import { mongoQueryMatcher, MongoQuery } from './matchers/conditions';
import { mongoQueryMatcher } from './matchers/conditions';
import { fieldPatternMatcher } from './matchers/field';

const DEFAULT_ALIASES: AliasesMap = {};
Expand All @@ -13,8 +13,8 @@ function hasAction(action: string, actions: string | string[]): boolean {

export type Unsubscribe = () => void;

export interface AbilityOptions<Conditions> {
subjectName?: GetSubjectName
export interface AbilityOptions<Subjects extends Subject, Conditions> {
subjectName?: GetSubjectName<Subjects>
conditionsMatcher?: ConditionsMatcher<Conditions>
fieldMatcher?: FieldMatcher
}
Expand Down Expand Up @@ -49,16 +49,16 @@ type EventsMap<A extends string, S extends Subject, C> = {

export class Ability<
Actions extends string = string,
Subjects extends Subject = string,
Conditions = MongoQuery
Subjects extends Subject = Subject,
Conditions = object
> {
private _hasPerFieldRules: boolean = false;
private _mergedRules: Record<string, Rule<Actions, Subjects, Conditions>[]> = {};
private _events: Events<Actions, Subjects, Conditions> = Object.create(null);
private _indexedRules: RuleIndex<Actions, Subjects, Conditions> = {};
private readonly _fieldMatcher: FieldMatcher;
private readonly _conditionsMatcher: ConditionsMatcher<Conditions>;
public readonly subjectName: GetSubjectName = getSubjectName;
public readonly subjectName: GetSubjectName<Subjects> = getSubjectName;
private _rules: Rule<Actions, Subjects, Conditions>[] = [];
public readonly rules: Rule<Actions, Subjects, Conditions>[] = [];

Expand All @@ -80,7 +80,7 @@ export class Ability<

constructor(
rules: RawRule<Actions, E<Subjects>, Conditions>[],
options: AbilityOptions<Conditions> = {}
options: AbilityOptions<Subjects, Conditions> = {}
) {
this._conditionsMatcher = options.conditionsMatcher
|| mongoQueryMatcher as unknown as ConditionsMatcher<Conditions>;
Expand Down
65 changes: 34 additions & 31 deletions packages/casl-ability/src/AbilityBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ class RuleBuilder<A extends string, S extends SubjectType, C> {
}
}

type ToA<T> = T | T[];
type AsyncDSL = <T extends Function>(can: T, cannot: T) => Promise<void>;
type DSL = <T extends Function>(can: T, cannot: T) => void;

Expand All @@ -26,30 +25,30 @@ export class AbilityBuilder<
Conditions
> {
static define<
A extends string,
S extends Subject,
C
A extends string = string,
S extends Subject = Subject,
C = object
>(dsl: AsyncDSL): Promise<Ability<A, S, C>>;
static define<
A extends string,
S extends Subject,
C
>(params: AbilityOptions<C>, dsl: AsyncDSL): Promise<Ability<A, S, C>>;
A extends string = string,
S extends Subject = Subject,
C = object
>(params: AbilityOptions<S, C>, dsl: AsyncDSL): Promise<Ability<A, S, C>>;
static define<
A extends string,
S extends Subject,
C
A extends string = string,
S extends Subject = Subject,
C = object
>(dsl: DSL): Ability<A, S, C>;
static define<
A extends string,
S extends Subject,
C
>(params: AbilityOptions<C>, dsl: DSL): Ability<A, S, C>;
A extends string = string,
S extends Subject = Subject,
C = object
>(params: AbilityOptions<S, C>, dsl: DSL): Ability<A, S, C>;
static define<A extends string, S extends Subject, C>(
params: AbilityOptions<C> | DSL | AsyncDSL,
params: AbilityOptions<S, C> | DSL | AsyncDSL,
dsl?: DSL | AsyncDSL
): Ability<A, S, C> | Promise<Ability<A, S, C>> {
let options: AbilityOptions<C>;
let options: AbilityOptions<S, C>;
let define: Function;

if (typeof params === 'function') {
Expand Down Expand Up @@ -77,7 +76,11 @@ export class AbilityBuilder<
: buildAbility();
}

static extract<A extends string, S extends Subject, C>(options?: AbilityOptions<C>) {
static extract<
A extends string = string,
S extends Subject = Subject,
C = object
>(options?: AbilityOptions<S, C>) {
const builder = new this<A, S, C>();

return {
Expand All @@ -95,20 +98,20 @@ export class AbilityBuilder<
}

can(
action: ToA<Actions>,
subject: ToA<E<Subjects>>,
action: Actions | Actions[],
subject: E<Subjects> | E<Subjects>[],
conditions?: Conditions
): RuleBuilder<Actions, E<Subjects>, Conditions>
can(
action: ToA<Actions>,
subject: ToA<E<Subjects>>,
fields: ToA<string>,
action: Actions | Actions[],
subject: E<Subjects> | E<Subjects>[],
fields: string | string[],
conditions?: Conditions
): RuleBuilder<Actions, E<Subjects>, Conditions>
can(
action: ToA<Actions>,
subject: ToA<E<Subjects>>,
conditionsOrFields?: ToA<string> | Conditions,
action: Actions | Actions[],
subject: E<Subjects> | E<Subjects>[],
conditionsOrFields?: string | string[] | Conditions,
conditions?: Conditions
): RuleBuilder<Actions, E<Subjects>, Conditions> {
if (!isStringOrNonEmptyArray(action)) {
Expand All @@ -135,14 +138,14 @@ export class AbilityBuilder<
}

cannot(
action: ToA<Actions>,
subject: ToA<E<Subjects>>,
action: Actions | Actions[],
subject: E<Subjects> | E<Subjects>[],
conditions?: Conditions
): RuleBuilder<Actions, E<Subjects>, Conditions>
cannot(
action: ToA<Actions>,
subject: ToA<E<Subjects>>,
fields: ToA<string>,
action: Actions | Actions[],
subject: E<Subjects> | E<Subjects>[],
fields: string | string[],
conditions?: Conditions
): RuleBuilder<Actions, E<Subjects>, Conditions>
cannot(...args: [any, any, any?, any?]): RuleBuilder<Actions, E<Subjects>, Conditions> {
Expand Down
2 changes: 2 additions & 0 deletions packages/casl-ability/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export { default as ForbiddenError } from './ForbiddenError';
export * from './RawRule';
export * from './types';
export { wrapArray } from './utils';
export * from './matchers/conditions';
export * from './matchers/field';
2 changes: 1 addition & 1 deletion packages/casl-ability/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export type SubjectConstructor<N extends string = string> = AnyClass & { modelNa
export type AnyObject = Record<PropertyKey, unknown>;
export type SubjectType = string | SubjectConstructor | AnyClass;
export type Subject = object | SubjectType;
export type GetSubjectName = (subject: Subject) => string;
export type GetSubjectName<T extends Subject> = (subject: T) => string;

export type ExtractSubjectType<S extends Subject> = Extract<S, SubjectType>;
export type CollectSubjects<T, IncludeTagName = unknown> =
Expand Down
14 changes: 9 additions & 5 deletions packages/casl-ability/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AnyObject, GetSubjectName, SubjectConstructor } from './types';
import { AnyObject, Subject, SubjectConstructor } from './types';

export function wrapArray<T>(value: T[] | T): T[] {
return Array.isArray(value) ? value : [value];
Expand All @@ -21,14 +21,18 @@ export function setByPath(object: AnyObject, path: string, value: unknown): void
ref[lastKey] = value;
}

export const getSubjectName: GetSubjectName = (subject) => {
if (!subject || typeof subject === 'string') {
export function getSubjectName<T extends Subject>(subject: T) {
if (!subject) {
throw new Error('subject cannot be falsy');
}

if (typeof subject === 'string') {
return subject;
}

const Type = typeof subject === 'object' ? subject.constructor : subject;
const Type = typeof subject === 'function' ? subject : subject.constructor;
return (Type as SubjectConstructor).modelName || Type.name;
};
}

export function isObject(value: unknown): value is object {
return value && typeof value === 'object';
Expand Down
6 changes: 3 additions & 3 deletions packages/casl-angular/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import { CanPipe } from './can';
})
export class AbilityModule {
static forRoot<
Actions extends string,
Subjects extends Subject,
Conditions
Actions extends string = string,
Subjects extends Subject = Subject,
Conditions = object
>(): ModuleWithProviders<AbilityModule> {
return {
ngModule: AbilityModule,
Expand Down

0 comments on commit c089293

Please sign in to comment.