TypeScript Guide
Complete guide to using Fire Shield with TypeScript for maximum type safety.
Why TypeScript?
TypeScript provides:
- Type Safety: Catch errors at compile time
- IntelliSense: Better IDE autocomplete
- Documentation: Self-documenting code
- Refactoring: Safe code changes
- Maintainability: Easier to understand and modify
Basic Types
RBACUser
The core user type with roles and permissions:
typescript
import type { RBACUser } from '@fire-shield/core';
const user: RBACUser = {
id: 'user-123',
roles: ['editor'],
permissions: ['beta:feature'], // Optional
permissionMask: 3 // Optional
};Extending RBACUser
Add custom fields to the user type:
typescript
interface AppUser extends RBACUser {
email: string;
name: string;
department: string;
createdAt: Date;
}
const user: AppUser = {
id: 'user-123',
roles: ['editor'],
email: 'user@example.com',
name: 'John Doe',
department: 'Engineering',
createdAt: new Date()
};
// Still works with RBAC
rbac.hasPermission(user, 'post:write');Type-Safe Permissions
Using Const Assertions
typescript
const PERMISSIONS = {
// User permissions
USER_READ: 'user:read',
USER_WRITE: 'user:write',
USER_DELETE: 'user:delete',
// Post permissions
POST_READ: 'post:read',
POST_WRITE: 'post:write',
POST_DELETE: 'post:delete',
// Admin permissions
ADMIN_ACCESS: 'admin:access',
} as const;
// Type-safe permission type
type Permission = typeof PERMISSIONS[keyof typeof PERMISSIONS];
// Type-safe permission check
function checkPermission(user: RBACUser, permission: Permission): boolean {
return rbac.hasPermission(user, permission);
}
// ✅ Valid
checkPermission(user, PERMISSIONS.USER_READ);
// ❌ TypeScript error
checkPermission(user, 'invalid:permission');Using String Literal Types
typescript
type Permission =
| 'user:read'
| 'user:write'
| 'user:delete'
| 'post:read'
| 'post:write'
| 'post:delete';
function hasPermission(user: RBACUser, permission: Permission): boolean {
return rbac.hasPermission(user, permission);
}
// ✅ Type-safe
hasPermission(user, 'user:read');
// ❌ TypeScript error
hasPermission(user, 'invalid:permission');Type-Safe Roles
String Literal Types
typescript
type Role = 'admin' | 'editor' | 'viewer';
function hasRole(user: RBACUser, role: Role): boolean {
return user.roles.includes(role);
}
// ✅ Type-safe
hasRole(user, 'admin');
// ❌ TypeScript error
hasRole(user, 'invalid-role');Role Configuration
typescript
interface RoleConfig {
name: Role;
permissions: Permission[];
level: number;
description: string;
}
const roles: RoleConfig[] = [
{
name: 'admin',
permissions: ['user:read', 'user:write', 'post:read', 'post:write'],
level: 10,
description: 'Administrator'
},
{
name: 'editor',
permissions: ['post:read', 'post:write'],
level: 5,
description: 'Content Editor'
},
{
name: 'viewer',
permissions: ['post:read'],
level: 1,
description: 'Viewer'
}
];Generic Functions
Type-Safe Permission Checker
typescript
function createPermissionChecker<T extends string>(
rbac: RBAC,
user: RBACUser
) {
return {
can: (permission: T): boolean => {
return rbac.hasPermission(user, permission);
},
canAll: (permissions: T[]): boolean => {
return rbac.hasAllPermissions(user, permissions);
},
canAny: (permissions: T[]): boolean => {
return rbac.hasAnyPermission(user, permissions);
}
};
}
// Usage
const checker = createPermissionChecker<Permission>(rbac, user);
checker.can('user:read'); // Type-safe ✅
checker.can('invalid'); // Type error ❌Type-Safe User Factory
typescript
function createUser<T extends RBACUser>(
id: string,
roles: Role[],
additional?: Omit<T, 'id' | 'roles'>
): T {
return {
id,
roles,
...additional
} as T;
}
// Usage
interface CustomUser extends RBACUser {
email: string;
name: string;
}
const user = createUser<CustomUser>('user-1', ['editor'], {
email: 'user@example.com',
name: 'John Doe'
});Discriminated Unions
Authorization Results
typescript
type AuthorizationSuccess = {
allowed: true;
user: RBACUser;
};
type AuthorizationFailure = {
allowed: false;
reason: string;
};
type AuthResult = AuthorizationSuccess | AuthorizationFailure;
function authorize(user: RBACUser, permission: Permission): AuthResult {
const result = rbac.authorize(user, permission);
if (result.allowed) {
return {
allowed: true,
user: result.user!
};
} else {
return {
allowed: false,
reason: result.reason || 'Permission denied'
};
}
}
// Type-safe usage
const result = authorize(user, 'user:read');
if (result.allowed) {
console.log(result.user); // Type: RBACUser
} else {
console.log(result.reason); // Type: string
}Type Guards
Custom Type Guards
typescript
function isRBACUser(value: unknown): value is RBACUser {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
typeof (value as any).id === 'string' &&
'roles' in value &&
Array.isArray((value as any).roles)
);
}
// Usage
function checkUser(maybeUser: unknown) {
if (isRBACUser(maybeUser)) {
// TypeScript knows this is an RBACUser
rbac.hasPermission(maybeUser, 'post:read');
}
}Advanced Type Guards
typescript
function hasPermissions<T extends string[]>(
user: RBACUser,
permissions: T
): user is RBACUser & { permissions: T } {
return permissions.every(p => rbac.hasPermission(user, p));
}
// Usage
if (hasPermissions(user, ['post:read', 'post:write'] as const)) {
// TypeScript knows user has these permissions
}Utility Types
WithMetadata
typescript
type WithMetadata<T> = T & {
metadata?: Record<string, any>;
};
type UserWithMetadata = WithMetadata<RBACUser>;
const user: UserWithMetadata = {
id: 'user-1',
roles: ['editor'],
metadata: {
department: 'Engineering',
hireDate: '2025-01-01'
}
};Partial Permissions
typescript
type PartialPermissions = {
[K in Permission]?: boolean;
};
function checkPermissions(user: RBACUser): PartialPermissions {
const perms: PartialPermissions = {};
for (const perm of Object.values(PERMISSIONS)) {
perms[perm] = rbac.hasPermission(user, perm);
}
return perms;
}
// Usage
const userPermissions = checkPermissions(user);
console.log(userPermissions['user:read']); // boolean | undefinedNamespaced Permissions
typescript
namespace Permissions {
export namespace User {
export const READ = 'user:read';
export const WRITE = 'user:write';
export const DELETE = 'user:delete';
}
export namespace Post {
export const READ = 'post:read';
export const WRITE = 'post:write';
export const DELETE = 'post:delete';
}
}
// Usage
rbac.hasPermission(user, Permissions.User.READ);
rbac.hasPermission(user, Permissions.Post.WRITE);Configuration Types
Strongly Typed Configuration
typescript
interface RBACConfigTyped {
roles: {
name: Role;
permissions: Permission[];
level: number;
}[];
permissions: {
name: Permission;
bit: number;
}[];
}
const config: RBACConfigTyped = {
roles: [
{ name: 'admin', permissions: ['user:read', 'user:write'], level: 10 }
],
permissions: [
{ name: 'user:read', bit: 1 },
{ name: 'user:write', bit: 2 }
]
};Best Practices
1. Define Types Early
typescript
// ✅ Good: Define at the start
type Permission = 'user:read' | 'user:write';
type Role = 'admin' | 'editor';
interface AppUser extends RBACUser {
email: string;
}
// Use throughout the app2. Use Const Assertions
typescript
// ✅ Good: Type-safe constants
const PERMISSIONS = {
USER_READ: 'user:read',
USER_WRITE: 'user:write'
} as const;
type Permission = typeof PERMISSIONS[keyof typeof PERMISSIONS];3. Avoid Any
typescript
// ❌ Avoid
function checkPermission(user: any, permission: any) {
return rbac.hasPermission(user, permission);
}
// ✅ Good
function checkPermission(user: RBACUser, permission: Permission) {
return rbac.hasPermission(user, permission);
}4. Use Generics
typescript
// ✅ Good: Generic type-safe wrapper
function withPermission<T extends RBACUser>(
user: T,
permission: Permission,
action: (user: T) => void
) {
if (rbac.hasPermission(user, permission)) {
action(user);
}
}5. Strict Mode
Enable strict mode in tsconfig.json:
json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true
}
}Framework-Specific Types
React
typescript
import type { RBACUser } from '@fire-shield/react';
interface Props {
user: RBACUser;
permission: Permission;
}
const ProtectedButton: React.FC<Props> = ({ user, permission }) => {
const { can } = useRBAC();
if (!can(permission)) {
return null;
}
return <button>Action</button>;
};Vue
typescript
import type { RBACUser } from '@fire-shield/vue';
interface UserState {
user: RBACUser | null;
isAuthenticated: boolean;
}
const userState = reactive<UserState>({
user: null,
isAuthenticated: false
});Express
typescript
import type { Request, Response } from 'express';
import type { RBACUser } from '@fire-shield/core';
// Extend Express Request type
declare global {
namespace Express {
interface Request {
user?: RBACUser;
}
}
}
// Type-safe middleware
function requirePermission(permission: Permission) {
return (req: Request, res: Response, next: Function) => {
if (!req.user || !rbac.hasPermission(req.user, permission)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}Testing with TypeScript
Type-Safe Tests
typescript
import { describe, it, expect } from 'vitest';
import type { RBACUser } from '@fire-shield/core';
describe('RBAC', () => {
const admin: RBACUser = {
id: 'admin-1',
roles: ['admin']
};
const viewer: RBACUser = {
id: 'viewer-1',
roles: ['viewer']
};
it('should allow admin to delete', () => {
expect(rbac.hasPermission(admin, 'post:delete')).toBe(true);
});
it('should deny viewer to delete', () => {
expect(rbac.hasPermission(viewer, 'post:delete')).toBe(false);
});
});Next Steps
- Explore Core API
- Learn about TypeScript Types
- Check out Performance Guide
