preact-missing-hooks
Version:
A lightweight, extendable collection of missing React-like hooks for Preact — plus fresh, powerful new ones designed specifically for modern Preact apps.
103 lines (102 loc) • 4.22 kB
TypeScript
/** Flexible user object; your app can use any shape. */
export type RBACUser = Record<string, unknown>;
/** Get auth state (user + optional roles/capabilities). Used by custom source. */
export interface RBACAuthState {
user?: RBACUser | null;
roles?: string[];
capabilities?: string[];
}
/** Pluggable source for current user (and optionally roles/capabilities). */
export type RBACUserSource = {
type: "localStorage";
key: string;
} | {
type: "sessionStorage";
key: string;
} | {
type: "api";
fetch: () => Promise<RBACUser>;
} | {
type: "memory";
getUser: () => RBACUser | null;
} | {
type: "custom";
getAuth: () => RBACAuthState | Promise<RBACAuthState>;
};
/** Role definition: name + condition to grant this role based on user. */
export interface RBACRoleDefinition {
role: string;
condition: (user: RBACUser | null) => boolean;
}
/** Role name -> list of capability strings. Use '*' for full access. */
export type RBACRoleCapabilities = Record<string, string[]>;
/** Optional override: get capabilities directly (e.g. from API) instead of deriving from roles. */
export type RBACCapabilitiesOverride = {
type: "localStorage";
key: string;
} | {
type: "sessionStorage";
key: string;
} | {
type: "api";
fetch: () => Promise<string[]>;
};
export interface UseRBACOptions {
/** Where to get the current user (and optionally roles/capabilities if type is 'custom'). */
userSource: RBACUserSource;
/** Role definitions: each role has a condition(user) to determine if the user has that role. */
roleDefinitions: RBACRoleDefinition[];
/** Capabilities per role. User gets union of capabilities for all their roles. Use '*' for admin. */
roleCapabilities: RBACRoleCapabilities;
/** Optional: fetch capabilities directly (overrides role-derived capabilities when provided). */
capabilitiesOverride?: RBACCapabilitiesOverride;
}
export interface UseRBACReturn {
/** Current user from source, or null. */
user: RBACUser | null;
/** Resolved roles for the current user. */
roles: string[];
/** Resolved capabilities (union of role capabilities, or from override). */
capabilities: string[];
/** True when user/roles/capabilities have been resolved (or failed). */
isReady: boolean;
/** Error from source (e.g. API or parse). */
error: Error | null;
/** Check if the user has the given role. */
hasRole: (role: string) => boolean;
/** Check if the user has the given capability (or '*' ). */
hasCapability: (capability: string) => boolean;
/** Alias for hasCapability. */
can: (capability: string) => boolean;
/** Re-fetch user/roles/capabilities from source. */
refetch: () => Promise<void>;
/** Helpers to persist auth to storage (for frontend-only flows). */
setUserInStorage: (user: RBACUser | null, storage: "localStorage" | "sessionStorage", key: string) => void;
setRolesInStorage: (roles: string[], storage: "localStorage" | "sessionStorage", key: string) => void;
setCapabilitiesInStorage: (capabilities: string[], storage: "localStorage" | "sessionStorage", key: string) => void;
}
/**
* Frontend-only role-based access control hook. Define roles with conditions,
* assign capabilities per role, and plug in user source (localStorage, sessionStorage, API, or custom).
* Supports full flexibility: frontend-only with storage or pluggable API.
*
* @param options - userSource, roleDefinitions, roleCapabilities, optional capabilitiesOverride
* @returns user, roles, capabilities, hasRole, hasCapability, can, isReady, error, refetch, and storage helpers
*
* @example
* ```tsx
* const { hasRole, can, roles, setUserInStorage } = useRBAC({
* userSource: { type: 'localStorage', key: 'user' },
* roleDefinitions: [
* { role: 'admin', condition: (u) => u?.role === 'admin' },
* { role: 'editor', condition: (u) => u?.role === 'editor' || u?.role === 'admin' },
* ],
* roleCapabilities: {
* admin: ['*'],
* editor: ['posts:edit', 'posts:create'],
* },
* });
* if (can('posts:edit')) { ... }
* ```
*/
export declare function useRBAC(options: UseRBACOptions): UseRBACReturn;