Skip to content

Commit

Permalink
Merge pull request #3 from nicolapalavecino/update/typings
Browse files Browse the repository at this point in the history
Improve typings
  • Loading branch information
victorybiz committed Apr 8, 2022
2 parents 7edd4dc + 4aaef7e commit 7c1ccdc
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 64 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
"module": "dist/vue-simple-acl.esm.js",
"unpkg": "dist/vue-simple-acl.min.js",
"jsdelivr": "dist/vue-simple-acl.min.js",
"typings": "types/index.d.ts",
"files": [
"dist/*",
"src/*"
"src/*",
"types/*"
],
"scripts": {
"dev": "vite",
Expand Down
2 changes: 1 addition & 1 deletion playground/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const user = {
// .then((response) => response.data);
// }

const rules = () => defineAclRules((setRule) => {
const rules = () => defineAclRules<typeof user>((setRule) => {
setRule('create-post', (user) => user.is_admin || user.is_editor);
setRule('is-admin', (user) => user.is_admin);
setRule('is-editor', (user) => user.is_editor);
Expand Down
26 changes: 0 additions & 26 deletions src/@types/index.ts

This file was deleted.

51 changes: 28 additions & 23 deletions src/VueSimpleAcl.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import { reactive, computed } from 'vue';
import { getFunctionArgsNames, capitalize } from './utils';
import { State, PluginOption, Ability, AbilityArgs, AbilitiesEvaluationProps } from './@types';
import { computed, reactive } from 'vue';

import { Ability, AbilityArgs, PluginOption, State, User } from '../types';
import { PluginOptionWithDefaults, AbilitiesEvaluationProps, Acl, AsyncUser, RuleSetter } from '../types/acl';
import { capitalize, getFunctionArgsNames } from './utils';

// plugin global state
const state = reactive({
registeredUser: {},
registeredRules: {},
options: {},
} as State);
} as State<unknown>);

/**
* Register plugin options to state
* @param pluginOptions
* @return void
*/
const registerPluginOptions = (pluginOptions: PluginOption): void => {
const registerPluginOptions = <U = User>(pluginOptions: PluginOptionWithDefaults<U>): void => {
// Init and set user to state
if (pluginOptions.user && typeof pluginOptions.user === "function") {
if (hasAsyncUser(pluginOptions.user)) {
state.registeredUser = pluginOptions.user();
} else {
state.registeredUser = pluginOptions.user;
Expand Down Expand Up @@ -262,28 +264,35 @@ const anyCanHelperHandler = (abilities: Ability, args?: AbilityArgs): boolean =>
return helperArgsToPrepareAcl({ abilities: abilities, args: args, any: true });
}

/**
* Checks if the user has an async getter
* @param {U|AsyncUser<U>} user
* @returns boolean
*/
const hasAsyncUser = <U = User>(user: U | AsyncUser<U>): user is AsyncUser<U> => {
return typeof user === 'function' && (user as any)() instanceof Promise;
};


/**
* Install the plugin
* @param app
* @param options
* @return void
*/
export const installPlugin = (app: any, options?: PluginOption) => {
export const installPlugin = <U = User>(app: any, options?: PluginOption<U>) => {

const isVue3: boolean = !!app.config.globalProperties;
let hasAsyncUser = false;
const defaultPluginOptions: PluginOption = {
const defaultPluginOptions: PluginOptionWithDefaults<U> = {
user: Object.create(null),
rules: null,
router: null,
onDeniedRoute: '/',
directiveName: 'can',
helperName: '$can',
enableSematicAlias: true
// enableAlias: true,
}
const pluginOptions: PluginOption = { ...defaultPluginOptions, ...options };
const pluginOptions: PluginOptionWithDefaults<U> = { ...defaultPluginOptions, ...options };

// Sanitize directive name should the developer specified a custom name
if (pluginOptions.directiveName && typeof pluginOptions.directiveName === "string") {
Expand All @@ -300,11 +309,7 @@ export const installPlugin = (app: any, options?: PluginOption) => {
}

// Register the plugin options to state
if (typeof pluginOptions.user === 'function' && pluginOptions.user() instanceof Promise) {
// when defined user is Asynchronous object or function
// It requires instance of a vue-router. See below for a router hook for async promise evaluation
hasAsyncUser = true;
} else {
if (!hasAsyncUser(pluginOptions.user)) {
// when defined user is an object or function but non-Asynchronous
registerPluginOptions(pluginOptions);
}
Expand Down Expand Up @@ -541,8 +546,8 @@ export const installPlugin = (app: any, options?: PluginOption) => {

// vue-router hook
pluginOptions.router.beforeEach((to: any, from: any, next: any) => {
if (hasAsyncUser) {
pluginOptions.user().then((user: PluginOption['user'])=> {
if (hasAsyncUser(pluginOptions.user)) {
pluginOptions.user().then(user => {
pluginOptions.user = user;
registerPluginOptions(pluginOptions);
evaluateRouterAcl(to, from, next);
Expand All @@ -555,7 +560,7 @@ export const installPlugin = (app: any, options?: PluginOption) => {
}
});
} else { // No router
if (hasAsyncUser) {
if (hasAsyncUser(pluginOptions.user)) {
console.error(`:::VueSimpleACL::: Instance of vue-router is required to define 'user' retrieved from a promise or Asynchronous function.`)
}
} // ./ Vue Router evaluation
Expand All @@ -567,10 +572,10 @@ export const installPlugin = (app: any, options?: PluginOption) => {
* @param userDefinedOptions
* @return object
*/
export const createAcl = (userDefinedOptions: PluginOption): object => {
export const createAcl = <U = User>(userDefinedOptions: PluginOption<U>): any => {
return {
install: (app: any, options: any = {}) => {
installPlugin(app, { ...options, ...userDefinedOptions });
installPlugin(app, { ...options, ...userDefinedOptions });
}
}
}
Expand All @@ -580,7 +585,7 @@ export const createAcl = (userDefinedOptions: PluginOption): object => {
* @param aclRulesCallback
* @return void
*/
export const defineAclRules = (aclRulesCallback: Function): void => {
export const defineAclRules = <U = User>(aclRulesCallback: (setter: RuleSetter<U>) => void): void => {
if (typeof aclRulesCallback === "function") {
aclRulesCallback(setRule);
}
Expand All @@ -590,7 +595,7 @@ export const defineAclRules = (aclRulesCallback: Function): void => {
* Returns the acl helper instance. Equivalent to using `$can` inside templates.
* @return object
*/
export const useAcl = () => {
export const useAcl = <U = User>(): Acl<U> => {
let acl: any = {};
acl.user = computed(() => state.registeredUser).value;
acl.getUser = () => state.registeredUser;
Expand Down
16 changes: 3 additions & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// VueSimpleAcl.js
import { PluginOption } from '../types';
import { installPlugin, createAcl, defineAclRules, useAcl } from './VueSimpleAcl';
import { PluginOption } from './@types';

/**
* The Plugin
Expand All @@ -9,23 +9,13 @@ const VueSimpleAcl = {
install: (app: any, options?: PluginOption) => installPlugin(app, options),
}


// necessary to fix typescript for automatic installation
declare global {
interface Window {
Vue: any;
}
}
// Automatic installation if Vue has been added to the global scope.
if (typeof window !== 'undefined' && window.Vue) {
window.Vue.use(VueSimpleAcl);
}

export {
// VueSimpleAcl,
createAcl,
defineAclRules,
useAcl
}

// export default VueSimpleAcl;
useAcl,
};
87 changes: 87 additions & 0 deletions types/acl.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { ComputedRef } from "@vue/reactivity";

// type User with properties and callable/call signature
export type User = {
[key: string]: any,
(): any, // callable / call Signature
}

export type AsyncUser<U> = () => Promise<U>;

export interface PluginOption<U = User> {
user?: U | (() => Promise<U>);
rules?: Function | null;
router?: any;
onDeniedRoute?: string;
directiveName?: string;
helperName?: string;
enableSematicAlias?: boolean;
}

export interface PluginOptionWithDefaults<U = User> {
user: U | (() => Promise<U>);
rules: Function | null;
router: any;
onDeniedRoute: string;
directiveName: string;
helperName: string;
enableSematicAlias: boolean;
}

export interface State<U = User> {
registeredUser: { [key: string]: any };
registeredRules: { [key: string]: any };
options: PluginOptionWithDefaults<U>;
}

export type Ability = string | any[] | null;
export type AbilityArgs = string | any[] | null;

export type AbilitiesEvaluationProps = { abilities?: Ability, args?: AbilityArgs, any?: boolean };
export type AbilitiesEvaluation = (abilities: Ability, args?: AbilityArgs) => boolean;
export type PolicyEvaluation<U = User> = (user: U, ...params: any[]) => boolean;
export type RuleSetter<U = User> = (abilities: Ability, callback: PolicyEvaluation<U>) => void;

export interface Acl<User> {
user: ComputedRef<User>;
getUser: () => User;
can: {
(abilities: Ability, args?: AbilityArgs): boolean;
not: AbilitiesEvaluation;
any: AbilitiesEvaluation;
}
notCan: AbilitiesEvaluation;
canNot: AbilitiesEvaluation;
cannot: AbilitiesEvaluation;
anyCan: AbilitiesEvaluation;
allPermission: AbilitiesEvaluation;
notPermission: AbilitiesEvaluation;
anyPermission: AbilitiesEvaluation;
permission: {
(abilities: Ability, args?: AbilityArgs): boolean;
not: AbilitiesEvaluation;
any: AbilitiesEvaluation;
}
role: {
(abilities: Ability, args?: AbilityArgs): boolean;
not: AbilitiesEvaluation;
any: AbilitiesEvaluation;
}
allRole: AbilitiesEvaluation;
notRole: AbilitiesEvaluation;
anyRole: AbilitiesEvaluation;
roleOrPermission: {
(abilities: Ability, args?: AbilityArgs): boolean;
not: AbilitiesEvaluation;
any: AbilitiesEvaluation;
}
allRoleOrPermission: AbilitiesEvaluation;
notRoleOrPermission: AbilitiesEvaluation;
anyRoleOrPermission: AbilitiesEvaluation;
}


export declare const createAcl: <U = User>(userDefinedOptions: PluginOption<U>) => any;
export declare const defineAclRules: <U = User>(aclRulesCallback: (setter: RuleSetter<U>) => void) => void;
export declare const useAcl: <U = User>() => Acl<U>;

14 changes: 14 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import './vue';

export {
User,
PluginOption,
State,
Ability,
AbilityArgs,
createAcl,
defineAclRules,
useAcl,
} from './acl';


22 changes: 22 additions & 0 deletions types/vue.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Augment the typings of Vue.js
*/

import { AbilitiesEvaluation } from './acl';

declare global {
interface Window {
Vue: any;
}
}

declare module 'vue/types/vue' {
interface Vue {
$can: {
(): AbilitiesEvaluation;
all: AbilitiesEvaluation;
not: AbilitiesEvaluation;
any: AbilitiesEvaluation;
}
}
}

0 comments on commit 7c1ccdc

Please sign in to comment.