1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct EnhancedSdkConfig {
12 pub base_url: String,
14 pub version: String,
16 pub typescript: bool,
18 pub include_rbac: bool,
20 pub include_conditional_permissions: bool,
22 pub include_audit: bool,
24 pub client_name: String,
26}
27
28impl Default for EnhancedSdkConfig {
29 fn default() -> Self {
30 Self {
31 base_url: "https://api.example.com".to_string(),
32 version: "v1".to_string(),
33 typescript: true,
34 include_rbac: true,
35 include_conditional_permissions: true,
36 include_audit: true,
37 client_name: "AuthFrameworkClient".to_string(),
38 }
39 }
40}
41
42pub struct JsSdkGenerator {
44 config: EnhancedSdkConfig,
45}
46
47impl JsSdkGenerator {
48 pub fn new(config: EnhancedSdkConfig) -> Self {
50 Self { config }
51 }
52
53 pub fn generate_sdk(&self) -> Result<HashMap<String, String>, Box<dyn std::error::Error>> {
55 let mut files = HashMap::new();
56
57 files.insert("client.ts".to_string(), self.generate_base_client()?);
59
60 if self.config.typescript {
62 files.insert("types.ts".to_string(), self.generate_types()?);
63 }
64
65 if self.config.include_rbac {
67 files.insert("rbac.ts".to_string(), self.generate_rbac_module()?);
68 }
69
70 if self.config.include_conditional_permissions {
72 files.insert(
73 "conditional.ts".to_string(),
74 self.generate_conditional_module()?,
75 );
76 }
77
78 if self.config.include_audit {
80 files.insert("audit.ts".to_string(), self.generate_audit_module()?);
81 }
82
83 files.insert("utils.ts".to_string(), self.generate_utils()?);
85
86 files.insert("index.ts".to_string(), self.generate_index()?);
88
89 files.insert("package.json".to_string(), self.generate_package_json()?);
91
92 files.insert("README.md".to_string(), self.generate_readme()?);
94
95 Ok(files)
96 }
97
98 fn generate_base_client(&self) -> Result<String, Box<dyn std::error::Error>> {
100 let client_code = format!(
101 r#"
102/**
103 * Enhanced {} with RBAC Support
104 * Generated by AuthFramework SDK Generator
105 */
106
107export interface ClientConfig {{
108 baseUrl: string;
109 apiKey?: string;
110 accessToken?: string;
111 timeout?: number;
112 retryAttempts?: number;
113}}
114
115export interface ApiResponse<T> {{
116 success: boolean;
117 data?: T;
118 error?: string;
119 message?: string;
120}}
121
122export class HttpError extends Error {{
123 constructor(
124 public status: number,
125 public statusText: string,
126 public body?: any
127 ) {{
128 super(`HTTP ${{status}}: ${{statusText}}`);
129 this.name = 'HttpError';
130 }}
131}}
132
133export class {} {{
134 private baseUrl: string;
135 private headers: Record<string, string> = {{}};
136 private timeout: number;
137 private retryAttempts: number;
138
139 constructor(config: ClientConfig) {{
140 this.baseUrl = config.baseUrl.replace(/\/$/, '');
141 this.timeout = config.timeout || 30000;
142 this.retryAttempts = config.retryAttempts || 3;
143
144 if (config.apiKey) {{
145 this.headers['X-API-Key'] = config.apiKey;
146 }}
147
148 if (config.accessToken) {{
149 this.headers['Authorization'] = `Bearer ${{config.accessToken}}`;
150 }}
151
152 this.headers['Content-Type'] = 'application/json';
153 }}
154
155 /**
156 * Set authentication token
157 */
158 setAccessToken(token: string): void {{
159 this.headers['Authorization'] = `Bearer ${{token}}`;
160 }}
161
162 /**
163 * Clear authentication token
164 */
165 clearAccessToken(): void {{
166 delete this.headers['Authorization'];
167 }}
168
169 /**
170 * Set API key
171 */
172 setApiKey(apiKey: string): void {{
173 this.headers['X-API-Key'] = apiKey;
174 }}
175
176 /**
177 * Make HTTP request with retry logic
178 */
179 private async makeRequest<T>(
180 method: string,
181 path: string,
182 body?: any,
183 headers?: Record<string, string>
184 ): Promise<ApiResponse<T>> {{
185 const url = `${{this.baseUrl}}${{path}}`;
186 const requestHeaders = {{ ...this.headers, ...headers }};
187
188 let lastError: Error | null = null;
189
190 for (let attempt = 0; attempt <= this.retryAttempts; attempt++) {{
191 try {{
192 const controller = new AbortController();
193 const timeoutId = setTimeout(() => controller.abort(), this.timeout);
194
195 const response = await fetch(url, {{
196 method,
197 headers: requestHeaders,
198 body: body ? JSON.stringify(body) : undefined,
199 signal: controller.signal,
200 }});
201
202 clearTimeout(timeoutId);
203
204 const responseData = await response.json();
205
206 if (!response.ok) {{
207 throw new HttpError(response.status, response.statusText, responseData);
208 }}
209
210 return responseData as ApiResponse<T>;
211 }} catch (error) {{
212 lastError = error as Error;
213
214 // Don't retry on client errors (4xx)
215 if (error instanceof HttpError && error.status >= 400 && error.status < 500) {{
216 throw error;
217 }}
218
219 // Wait before retry (exponential backoff)
220 if (attempt < this.retryAttempts) {{
221 await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
222 }}
223 }}
224 }}
225
226 throw lastError || new Error('Request failed after all retries');
227 }}
228
229 /**
230 * GET request
231 */
232 async get<T>(path: string, headers?: Record<string, string>): Promise<ApiResponse<T>> {{
233 return this.makeRequest<T>('GET', path, undefined, headers);
234 }}
235
236 /**
237 * POST request
238 */
239 async post<T>(path: string, body?: any, headers?: Record<string, string>): Promise<ApiResponse<T>> {{
240 return this.makeRequest<T>('POST', path, body, headers);
241 }}
242
243 /**
244 * PUT request
245 */
246 async put<T>(path: string, body?: any, headers?: Record<string, string>): Promise<ApiResponse<T>> {{
247 return this.makeRequest<T>('PUT', path, body, headers);
248 }}
249
250 /**
251 * DELETE request
252 */
253 async delete<T>(path: string, headers?: Record<string, string>): Promise<ApiResponse<T>> {{
254 return this.makeRequest<T>('DELETE', path, undefined, headers);
255 }}
256
257 /**
258 * PATCH request
259 */
260 async patch<T>(path: string, body?: any, headers?: Record<string, string>): Promise<ApiResponse<T>> {{
261 return this.makeRequest<T>('PATCH', path, body, headers);
262 }}
263}}
264"#,
265 self.config.client_name, self.config.client_name
266 );
267
268 Ok(client_code)
269 }
270
271 fn generate_types(&self) -> Result<String, Box<dyn std::error::Error>> {
273 let types_code = r#"
274/**
275 * TypeScript type definitions for AuthFramework RBAC
276 */
277
278export interface Role {
279 id: string;
280 name: string;
281 description?: string;
282 parent_id?: string;
283 permissions: string[];
284 created_at: string;
285 updated_at: string;
286}
287
288export interface Permission {
289 id: string;
290 action: string;
291 resource: string;
292 conditions?: Record<string, string>;
293 created_at: string;
294}
295
296export interface UserRole {
297 role_id: string;
298 role_name: string;
299 assigned_at: string;
300 assigned_by?: string;
301 expires_at?: string;
302}
303
304export interface UserRolesResponse {
305 user_id: string;
306 roles: UserRole[];
307 effective_permissions: string[];
308}
309
310export interface CreateRoleRequest {
311 name: string;
312 description?: string;
313 parent_id?: string;
314 permissions?: string[];
315}
316
317export interface UpdateRoleRequest {
318 name?: string;
319 description?: string;
320 parent_id?: string;
321}
322
323export interface AssignRoleRequest {
324 role_id: string;
325 expires_at?: string;
326 reason?: string;
327}
328
329export interface BulkAssignRequest {
330 assignments: BulkAssignment[];
331}
332
333export interface BulkAssignment {
334 user_id: string;
335 role_id: string;
336 expires_at?: string;
337}
338
339export interface ElevateRoleRequest {
340 target_role: string;
341 duration_minutes?: number;
342 justification: string;
343}
344
345export interface PermissionCheckRequest {
346 action: string;
347 resource: string;
348 context?: Record<string, string>;
349}
350
351export interface PermissionCheckResponse {
352 granted: boolean;
353 reason: string;
354 required_roles: string[];
355 missing_permissions: string[];
356}
357
358export interface AuditEntry {
359 id: string;
360 user_id?: string;
361 action: string;
362 resource?: string;
363 result: string;
364 context: Record<string, string>;
365 timestamp: string;
366}
367
368export interface AuditLogResponse {
369 entries: AuditEntry[];
370 total_count: number;
371 page: number;
372 per_page: number;
373}
374
375export interface AuditQuery {
376 user_id?: string;
377 action?: string;
378 resource?: string;
379 start_time?: string;
380 end_time?: string;
381 page?: number;
382 per_page?: number;
383}
384
385export interface ConditionalContext {
386 time_of_day?: 'business_hours' | 'after_hours' | 'weekend' | 'holiday';
387 day_type?: 'weekday' | 'weekend' | 'holiday';
388 device_type?: 'desktop' | 'mobile' | 'tablet' | 'unknown';
389 connection_type?: 'direct' | 'vpn' | 'proxy' | 'tor' | 'corporate' | 'unknown';
390 security_level?: 'low' | 'medium' | 'high' | 'critical';
391 risk_score?: number;
392 ip_address?: string;
393 user_agent?: string;
394 custom_attributes?: Record<string, string>;
395}
396
397export interface RoleListQuery {
398 page?: number;
399 per_page?: number;
400 parent_id?: string;
401 include_permissions?: boolean;
402}
403
404export type TimeOfDay = 'business_hours' | 'after_hours' | 'weekend' | 'holiday';
405export type DayType = 'weekday' | 'weekend' | 'holiday';
406export type DeviceType = 'desktop' | 'mobile' | 'tablet' | 'unknown';
407export type ConnectionType = 'direct' | 'vpn' | 'proxy' | 'tor' | 'corporate' | 'unknown';
408export type SecurityLevel = 'low' | 'medium' | 'high' | 'critical';
409"#;
410
411 Ok(types_code.to_string())
412 }
413
414 fn generate_rbac_module(&self) -> Result<String, Box<dyn std::error::Error>> {
416 let rbac_code = format!(
417 r#"
418/**
419 * RBAC (Role-Based Access Control) Module
420 * Provides comprehensive role and permission management
421 */
422
423import {{ {} }} from './client';
424import {{
425 Role, CreateRoleRequest, UpdateRoleRequest, UserRolesResponse,
426 AssignRoleRequest, BulkAssignRequest, ElevateRoleRequest,
427 PermissionCheckRequest, PermissionCheckResponse, RoleListQuery,
428 ApiResponse
429}} from './types';
430
431export class RbacManager {{
432 constructor(private client: {}) {{}}
433
434 // ============================================================================
435 // ROLE MANAGEMENT
436 // ============================================================================
437
438 /**
439 * Create a new role
440 */
441 async createRole(request: CreateRoleRequest): Promise<ApiResponse<Role>> {{
442 return this.client.post<Role>('/{}/rbac/roles', request);
443 }}
444
445 /**
446 * Get role by ID
447 */
448 async getRole(roleId: string): Promise<ApiResponse<Role>> {{
449 return this.client.get<Role>(`/{}/rbac/roles/${{roleId}}`);
450 }}
451
452 /**
453 * List all roles with pagination
454 */
455 async listRoles(query?: RoleListQuery): Promise<ApiResponse<Role[]>> {{
456 const params = new URLSearchParams();
457 if (query?.page) params.append('page', query.page.toString());
458 if (query?.per_page) params.append('per_page', query.per_page.toString());
459 if (query?.parent_id) params.append('parent_id', query.parent_id);
460 if (query?.include_permissions) params.append('include_permissions', query.include_permissions.toString());
461
462 const queryString = params.toString();
463 const path = queryString ? `/{}/rbac/roles?${{queryString}}` : '/{}/rbac/roles';
464
465 return this.client.get<Role[]>(path);
466 }}
467
468 /**
469 * Update an existing role
470 */
471 async updateRole(roleId: string, request: UpdateRoleRequest): Promise<ApiResponse<Role>> {{
472 return this.client.put<Role>(`/{}/rbac/roles/${{roleId}}`, request);
473 }}
474
475 /**
476 * Delete a role
477 */
478 async deleteRole(roleId: string): Promise<ApiResponse<void>> {{
479 return this.client.delete<void>(`/{}/rbac/roles/${{roleId}}`);
480 }}
481
482 // ============================================================================
483 // USER ROLE ASSIGNMENTS
484 // ============================================================================
485
486 /**
487 * Assign role to user
488 */
489 async assignUserRole(userId: string, request: AssignRoleRequest): Promise<ApiResponse<void>> {{
490 return this.client.post<void>(`/{}/rbac/users/${{userId}}/roles`, request);
491 }}
492
493 /**
494 * Revoke role from user
495 */
496 async revokeUserRole(userId: string, roleId: string): Promise<ApiResponse<void>> {{
497 return this.client.delete<void>(`/{}/rbac/users/${{userId}}/roles/${{roleId}}`);
498 }}
499
500 /**
501 * Get user's roles and effective permissions
502 */
503 async getUserRoles(userId: string): Promise<ApiResponse<UserRolesResponse>> {{
504 return this.client.get<UserRolesResponse>(`/{}/rbac/users/${{userId}}/roles`);
505 }}
506
507 /**
508 * Bulk assign roles to multiple users
509 */
510 async bulkAssignRoles(request: BulkAssignRequest): Promise<ApiResponse<void>> {{
511 return this.client.post<void>('/{}/rbac/bulk/assign', request);
512 }}
513
514 // ============================================================================
515 // PERMISSION CHECKING
516 // ============================================================================
517
518 /**
519 * Check if current user has permission
520 */
521 async checkPermission(request: PermissionCheckRequest): Promise<ApiResponse<PermissionCheckResponse>> {{
522 return this.client.post<PermissionCheckResponse>('/{}/rbac/check-permission', request);
523 }}
524
525 /**
526 * Quick permission check (returns boolean)
527 */
528 async hasPermission(action: string, resource: string, context?: Record<string, string>): Promise<boolean> {{
529 try {{
530 const response = await this.checkPermission({{ action, resource, context }});
531 return response.data?.granted || false;
532 }} catch (error) {{
533 console.error('Permission check failed:', error);
534 return false;
535 }}
536 }}
537
538 /**
539 * Request role elevation
540 */
541 async elevateRole(request: ElevateRoleRequest): Promise<ApiResponse<void>> {{
542 return this.client.post<void>('/{}/rbac/elevate', request);
543 }}
544
545 // ============================================================================
546 // CONVENIENCE METHODS
547 // ============================================================================
548
549 /**
550 * Check if user has any of the specified roles
551 */
552 async userHasAnyRole(userId: string, roleNames: string[]): Promise<boolean> {{
553 try {{
554 const response = await this.getUserRoles(userId);
555 if (!response.data) return false;
556
557 const userRoleNames = response.data.roles.map(r => r.role_name);
558 return roleNames.some(role => userRoleNames.includes(role));
559 }} catch (error) {{
560 console.error('Role check failed:', error);
561 return false;
562 }}
563 }}
564
565 /**
566 * Check if user has all of the specified roles
567 */
568 async userHasAllRoles(userId: string, roleNames: string[]): Promise<boolean> {{
569 try {{
570 const response = await this.getUserRoles(userId);
571 if (!response.data) return false;
572
573 const userRoleNames = response.data.roles.map(r => r.role_name);
574 return roleNames.every(role => userRoleNames.includes(role));
575 }} catch (error) {{
576 console.error('Role check failed:', error);
577 return false;
578 }}
579 }}
580
581 /**
582 * Get role hierarchy (parent-child relationships)
583 */
584 async getRoleHierarchy(): Promise<Record<string, string[]>> {{
585 try {{
586 const response = await this.listRoles({{ include_permissions: false }});
587 if (!response.data) return {{}};
588
589 const hierarchy: Record<string, string[]> = {{}};
590
591 for (const role of response.data) {{
592 if (role.parent_id) {{
593 if (!hierarchy[role.parent_id]) {{
594 hierarchy[role.parent_id] = [];
595 }}
596 hierarchy[role.parent_id].push(role.id);
597 }}
598 }}
599
600 return hierarchy;
601 }} catch (error) {{
602 console.error('Failed to build role hierarchy:', error);
603 return {{}};
604 }}
605 }}
606
607 /**
608 * Get all child roles for a given parent role
609 */
610 async getChildRoles(parentRoleId: string): Promise<Role[]> {{
611 try {{
612 const response = await this.listRoles({{ parent_id: parentRoleId }});
613 return response.data || [];
614 }} catch (error) {{
615 console.error('Failed to get child roles:', error);
616 return [];
617 }}
618 }}
619}}
620"#,
621 self.config.client_name,
622 self.config.client_name,
623 self.config.version,
624 self.config.version,
625 self.config.version,
626 self.config.version,
627 self.config.version,
628 self.config.version,
629 self.config.version,
630 self.config.version,
631 self.config.version,
632 self.config.version,
633 self.config.version,
634 self.config.version
635 );
636
637 Ok(rbac_code)
638 }
639
640 fn generate_conditional_module(&self) -> Result<String, Box<dyn std::error::Error>> {
642 let conditional_code = r#"
643/**
644 * Conditional Permissions Module
645 * Provides context-aware permission checking based on time, location, device, etc.
646 */
647
648import { ConditionalContext, PermissionCheckRequest, PermissionCheckResponse, ApiResponse } from './types';
649
650export class ConditionalPermissions {
651 constructor(private rbacManager: any) {}
652
653 /**
654 * Build context from current browser/environment
655 */
656 buildContext(): ConditionalContext {
657 const context: ConditionalContext = {};
658
659 // Time-based context
660 const now = new Date();
661 const hour = now.getHours();
662 const dayOfWeek = now.getDay(); // 0 = Sunday, 6 = Saturday
663
664 if (hour >= 9 && hour < 17 && dayOfWeek >= 1 && dayOfWeek <= 5) {
665 context.time_of_day = 'business_hours';
666 context.day_type = 'weekday';
667 } else if (dayOfWeek === 0 || dayOfWeek === 6) {
668 context.time_of_day = 'weekend';
669 context.day_type = 'weekend';
670 } else {
671 context.time_of_day = 'after_hours';
672 context.day_type = 'weekday';
673 }
674
675 // Device detection
676 if (navigator.userAgent) {
677 const ua = navigator.userAgent.toLowerCase();
678 if (ua.includes('mobile') || ua.includes('android') || ua.includes('iphone')) {
679 context.device_type = 'mobile';
680 } else if (ua.includes('tablet') || ua.includes('ipad')) {
681 context.device_type = 'tablet';
682 } else {
683 context.device_type = 'desktop';
684 }
685
686 context.user_agent = navigator.userAgent;
687 }
688
689 // Connection type detection (basic)
690 if (navigator.connection && (navigator.connection as any).effectiveType) {
691 const connection = navigator.connection as any;
692 if (connection.effectiveType === 'slow-2g' || connection.effectiveType === '2g') {
693 context.connection_type = 'proxy'; // Might indicate proxy/VPN
694 } else {
695 context.connection_type = 'direct';
696 }
697 }
698
699 return context;
700 }
701
702 /**
703 * Check permission with current environmental context
704 */
705 async checkPermissionWithContext(
706 action: string,
707 resource: string,
708 additionalContext?: Record<string, string>
709 ): Promise<ApiResponse<PermissionCheckResponse>> {
710 const context = this.buildContext();
711 const contextMap: Record<string, string> = {};
712
713 // Convert context to string map
714 if (context.time_of_day) contextMap.time_of_day = context.time_of_day;
715 if (context.day_type) contextMap.day_type = context.day_type;
716 if (context.device_type) contextMap.device_type = context.device_type;
717 if (context.connection_type) contextMap.connection_type = context.connection_type;
718 if (context.user_agent) contextMap.user_agent = context.user_agent;
719
720 // Add any additional context
721 if (additionalContext) {
722 Object.assign(contextMap, additionalContext);
723 }
724
725 return this.rbacManager.checkPermission({
726 action,
727 resource,
728 context: contextMap
729 });
730 }
731
732 /**
733 * Check if current time is within business hours
734 */
735 isBusinessHours(): boolean {
736 const now = new Date();
737 const hour = now.getHours();
738 const dayOfWeek = now.getDay();
739
740 return hour >= 9 && hour < 17 && dayOfWeek >= 1 && dayOfWeek <= 5;
741 }
742
743 /**
744 * Check if current day is weekend
745 */
746 isWeekend(): boolean {
747 const dayOfWeek = new Date().getDay();
748 return dayOfWeek === 0 || dayOfWeek === 6;
749 }
750
751 /**
752 * Get device type from user agent
753 */
754 getDeviceType(): 'desktop' | 'mobile' | 'tablet' | 'unknown' {
755 if (!navigator.userAgent) return 'unknown';
756
757 const ua = navigator.userAgent.toLowerCase();
758 if (ua.includes('mobile') || ua.includes('android') || ua.includes('iphone')) {
759 return 'mobile';
760 } else if (ua.includes('tablet') || ua.includes('ipad')) {
761 return 'tablet';
762 } else if (ua.includes('mozilla') || ua.includes('chrome') || ua.includes('firefox')) {
763 return 'desktop';
764 } else {
765 return 'unknown';
766 }
767 }
768
769 /**
770 * Calculate basic risk score based on context
771 */
772 calculateRiskScore(context?: ConditionalContext): number {
773 let risk = 0;
774 const ctx = context || this.buildContext();
775
776 // Time-based risk
777 if (ctx.time_of_day === 'after_hours') risk += 10;
778 if (ctx.day_type === 'weekend') risk += 5;
779
780 // Device-based risk
781 if (ctx.device_type === 'mobile') risk += 5;
782 if (ctx.device_type === 'unknown') risk += 15;
783
784 // Connection-based risk
785 if (ctx.connection_type === 'proxy') risk += 20;
786 if (ctx.connection_type === 'tor') risk += 50;
787
788 return Math.min(risk, 100); // Cap at 100
789 }
790
791 /**
792 * Create context for high-security operations
793 */
794 createHighSecurityContext(): Record<string, string> {
795 const context = this.buildContext();
796 const contextMap: Record<string, string> = {};
797
798 // Convert to string map
799 if (context.time_of_day) contextMap.time_of_day = context.time_of_day;
800 if (context.day_type) contextMap.day_type = context.day_type;
801 if (context.device_type) contextMap.device_type = context.device_type;
802 if (context.connection_type) contextMap.connection_type = context.connection_type;
803
804 // Add security requirements
805 contextMap.security_level = 'high';
806 contextMap.require_business_hours = 'true';
807 contextMap.block_vpn = 'true';
808 contextMap.max_risk_score = '20';
809
810 return contextMap;
811 }
812}
813"#;
814
815 Ok(conditional_code.to_string())
816 }
817
818 fn generate_audit_module(&self) -> Result<String, Box<dyn std::error::Error>> {
820 let audit_code = format!(
821 r#"
822/**
823 * Audit Logging Module
824 * Provides comprehensive audit trail functionality
825 */
826
827import {{ {} }} from './client';
828import {{ AuditLogResponse, AuditQuery, AuditEntry, ApiResponse }} from './types';
829
830export class AuditManager {{
831 constructor(private client: {}) {{}}
832
833 /**
834 * Get audit logs with filtering and pagination
835 */
836 async getAuditLogs(query?: AuditQuery): Promise<ApiResponse<AuditLogResponse>> {{
837 const params = new URLSearchParams();
838
839 if (query?.user_id) params.append('user_id', query.user_id);
840 if (query?.action) params.append('action', query.action);
841 if (query?.resource) params.append('resource', query.resource);
842 if (query?.start_time) params.append('start_time', query.start_time);
843 if (query?.end_time) params.append('end_time', query.end_time);
844 if (query?.page) params.append('page', query.page.toString());
845 if (query?.per_page) params.append('per_page', query.per_page.toString());
846
847 const queryString = params.toString();
848 const path = queryString ? `/{}/rbac/audit?${{queryString}}` : '/{}/rbac/audit';
849
850 return this.client.get<AuditLogResponse>(path);
851 }}
852
853 /**
854 * Get audit logs for specific user
855 */
856 async getUserAuditLogs(
857 userId: string,
858 options?: {{ startTime?: string; endTime?: string; page?: number; perPage?: number }}
859 ): Promise<ApiResponse<AuditLogResponse>> {{
860 return this.getAuditLogs({{
861 user_id: userId,
862 start_time: options?.startTime,
863 end_time: options?.endTime,
864 page: options?.page,
865 per_page: options?.perPage
866 }});
867 }}
868
869 /**
870 * Get audit logs for specific action
871 */
872 async getActionAuditLogs(
873 action: string,
874 options?: {{ startTime?: string; endTime?: string; page?: number; perPage?: number }}
875 ): Promise<ApiResponse<AuditLogResponse>> {{
876 return this.getAuditLogs({{
877 action,
878 start_time: options?.startTime,
879 end_time: options?.endTime,
880 page: options?.page,
881 per_page: options?.perPage
882 }});
883 }}
884
885 /**
886 * Get audit logs for specific resource
887 */
888 async getResourceAuditLogs(
889 resource: string,
890 options?: {{ startTime?: string; endTime?: string; page?: number; perPage?: number }}
891 ): Promise<ApiResponse<AuditLogResponse>> {{
892 return this.getAuditLogs({{
893 resource,
894 start_time: options?.startTime,
895 end_time: options?.endTime,
896 page: options?.page,
897 per_page: options?.perPage
898 }});
899 }}
900
901 /**
902 * Get audit logs for the last N hours
903 */
904 async getRecentAuditLogs(hours: number = 24): Promise<ApiResponse<AuditLogResponse>> {{
905 const endTime = new Date();
906 const startTime = new Date(endTime.getTime() - (hours * 60 * 60 * 1000));
907
908 return this.getAuditLogs({{
909 start_time: startTime.toISOString(),
910 end_time: endTime.toISOString()
911 }});
912 }}
913
914 /**
915 * Export audit logs as CSV
916 */
917 async exportAuditLogs(query?: AuditQuery): Promise<string> {{
918 try {{
919 const response = await this.getAuditLogs(query);
920 if (!response.data?.entries) {{
921 return '';
922 }}
923
924 const headers = ['Timestamp', 'User ID', 'Action', 'Resource', 'Result', 'Context'];
925 const rows = [headers.join(',')];
926
927 for (const entry of response.data.entries) {{
928 const row = [
929 entry.timestamp,
930 entry.user_id || '',
931 entry.action,
932 entry.resource || '',
933 entry.result,
934 JSON.stringify(entry.context).replace(/"/g, '""') // Escape quotes for CSV
935 ];
936 rows.push(row.map(field => `"${{field}}"`).join(','));
937 }}
938
939 return rows.join('\\n');
940 }} catch (error) {{
941 console.error('Failed to export audit logs:', error);
942 throw error;
943 }}
944 }}
945
946 /**
947 * Get audit statistics
948 */
949 async getAuditStatistics(
950 startTime?: string,
951 endTime?: string
952 ): Promise<{{
953 totalEntries: number;
954 actionCounts: Record<string, number>;
955 userCounts: Record<string, number>;
956 successRate: number;
957 }}> {{
958 try {{
959 const response = await this.getAuditLogs({{
960 start_time: startTime,
961 end_time: endTime,
962 per_page: 1000 // Get a large sample
963 }});
964
965 if (!response.data?.entries) {{
966 return {{
967 totalEntries: 0,
968 actionCounts: {{}},
969 userCounts: {{}},
970 successRate: 0
971 }};
972 }}
973
974 const entries = response.data.entries;
975 const actionCounts: Record<string, number> = {{}};
976 const userCounts: Record<string, number> = {{}};
977 let successCount = 0;
978
979 for (const entry of entries) {{
980 // Count actions
981 actionCounts[entry.action] = (actionCounts[entry.action] || 0) + 1;
982
983 // Count users
984 if (entry.user_id) {{
985 userCounts[entry.user_id] = (userCounts[entry.user_id] || 0) + 1;
986 }}
987
988 // Count successes
989 if (entry.result === 'success' || entry.result === 'granted') {{
990 successCount++;
991 }}
992 }}
993
994 return {{
995 totalEntries: response.data.total_count,
996 actionCounts,
997 userCounts,
998 successRate: entries.length > 0 ? (successCount / entries.length) * 100 : 0
999 }};
1000 }} catch (error) {{
1001 console.error('Failed to get audit statistics:', error);
1002 throw error;
1003 }}
1004 }}
1005
1006 /**
1007 * Monitor real-time audit events (if supported by backend)
1008 */
1009 monitorAuditEvents(
1010 callback: (entry: AuditEntry) => void,
1011 filters?: {{ userId?: string; action?: string; resource?: string }}
1012 ): () => void {{
1013 // This would typically use WebSockets or Server-Sent Events
1014 // For now, we'll implement polling as a fallback
1015
1016 let isMonitoring = true;
1017 let lastTimestamp = new Date().toISOString();
1018
1019 const poll = async () => {{
1020 if (!isMonitoring) return;
1021
1022 try {{
1023 const response = await this.getAuditLogs({{
1024 user_id: filters?.userId,
1025 action: filters?.action,
1026 resource: filters?.resource,
1027 start_time: lastTimestamp,
1028 per_page: 50
1029 }});
1030
1031 if (response.data?.entries) {{
1032 for (const entry of response.data.entries) {{
1033 callback(entry);
1034 if (entry.timestamp > lastTimestamp) {{
1035 lastTimestamp = entry.timestamp;
1036 }}
1037 }}
1038 }}
1039 }} catch (error) {{
1040 console.error('Error polling audit events:', error);
1041 }}
1042
1043 // Poll every 5 seconds
1044 setTimeout(poll, 5000);
1045 }};
1046
1047 // Start polling
1048 poll();
1049
1050 // Return cleanup function
1051 return () => {{
1052 isMonitoring = false;
1053 }};
1054 }}
1055}}
1056"#,
1057 self.config.client_name,
1058 self.config.client_name,
1059 self.config.version,
1060 self.config.version
1061 );
1062
1063 Ok(audit_code)
1064 }
1065
1066 fn generate_utils(&self) -> Result<String, Box<dyn std::error::Error>> {
1068 let utils_code = r#"
1069/**
1070 * Utility functions for AuthFramework SDK
1071 */
1072
1073/**
1074 * Sleep for specified milliseconds
1075 */
1076export function sleep(ms: number): Promise<void> {
1077 return new Promise(resolve => setTimeout(resolve, ms));
1078}
1079
1080/**
1081 * Retry a function with exponential backoff
1082 */
1083export async function retryWithBackoff<T>(
1084 fn: () => Promise<T>,
1085 maxAttempts: number = 3,
1086 baseDelay: number = 1000
1087): Promise<T> {
1088 let lastError: Error | null = null;
1089
1090 for (let attempt = 0; attempt < maxAttempts; attempt++) {
1091 try {
1092 return await fn();
1093 } catch (error) {
1094 lastError = error as Error;
1095
1096 if (attempt < maxAttempts - 1) {
1097 const delay = baseDelay * Math.pow(2, attempt);
1098 await sleep(delay);
1099 }
1100 }
1101 }
1102
1103 throw lastError || new Error('All retry attempts failed');
1104}
1105
1106/**
1107 * Debounce function calls
1108 */
1109export function debounce<T extends (...args: any[]) => any>(
1110 func: T,
1111 wait: number
1112): (...args: Parameters<T>) => void {
1113 let timeout: NodeJS.Timeout | null = null;
1114
1115 return function(this: any, ...args: Parameters<T>) {
1116 const later = () => {
1117 timeout = null;
1118 func.apply(this, args);
1119 };
1120
1121 if (timeout) {
1122 clearTimeout(timeout);
1123 }
1124 timeout = setTimeout(later, wait);
1125 };
1126}
1127
1128/**
1129 * Throttle function calls
1130 */
1131export function throttle<T extends (...args: any[]) => any>(
1132 func: T,
1133 limit: number
1134): (...args: Parameters<T>) => void {
1135 let inThrottle: boolean;
1136
1137 return function(this: any, ...args: Parameters<T>) {
1138 if (!inThrottle) {
1139 func.apply(this, args);
1140 inThrottle = true;
1141 setTimeout(() => inThrottle = false, limit);
1142 }
1143 };
1144}
1145
1146/**
1147 * Format date for API requests
1148 */
1149export function formatDateForApi(date: Date): string {
1150 return date.toISOString();
1151}
1152
1153/**
1154 * Parse API date response
1155 */
1156export function parseApiDate(dateString: string): Date {
1157 return new Date(dateString);
1158}
1159
1160/**
1161 * Check if error is network-related
1162 */
1163export function isNetworkError(error: any): boolean {
1164 return error instanceof TypeError && error.message.includes('fetch');
1165}
1166
1167/**
1168 * Check if error is authentication-related
1169 */
1170export function isAuthError(error: any): boolean {
1171 return error.status === 401 || error.status === 403;
1172}
1173
1174/**
1175 * Check if error is rate limit-related
1176 */
1177export function isRateLimitError(error: any): boolean {
1178 return error.status === 429;
1179}
1180
1181/**
1182 * Extract error message from API response
1183 */
1184export function extractErrorMessage(error: any): string {
1185 if (error.body?.message) return error.body.message;
1186 if (error.body?.error) return error.body.error;
1187 if (error.message) return error.message;
1188 return 'Unknown error occurred';
1189}
1190
1191/**
1192 * Validate email format
1193 */
1194export function isValidEmail(email: string): boolean {
1195 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1196 return emailRegex.test(email);
1197}
1198
1199/**
1200 * Generate UUID v4
1201 */
1202export function generateUuid(): string {
1203 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
1204 const r = Math.random() * 16 | 0;
1205 const v = c === 'x' ? r : (r & 0x3 | 0x8);
1206 return v.toString(16);
1207 });
1208}
1209
1210/**
1211 * Deep merge objects
1212 */
1213export function deepMerge<T>(target: T, source: Partial<T>): T {
1214 const output = { ...target };
1215
1216 if (isObject(target) && isObject(source)) {
1217 Object.keys(source).forEach(key => {
1218 if (isObject(source[key as keyof T])) {
1219 if (!(key in target)) {
1220 Object.assign(output, { [key]: source[key as keyof T] });
1221 } else {
1222 (output as any)[key] = deepMerge(target[key as keyof T], source[key as keyof T]);
1223 }
1224 } else {
1225 Object.assign(output, { [key]: source[key as keyof T] });
1226 }
1227 });
1228 }
1229
1230 return output;
1231}
1232
1233function isObject(item: any): boolean {
1234 return item && typeof item === 'object' && !Array.isArray(item);
1235}
1236
1237/**
1238 * Local storage wrapper with error handling
1239 */
1240export class SafeStorage {
1241 static get(key: string): string | null {
1242 try {
1243 return localStorage.getItem(key);
1244 } catch {
1245 return null;
1246 }
1247 }
1248
1249 static set(key: string, value: string): boolean {
1250 try {
1251 localStorage.setItem(key, value);
1252 return true;
1253 } catch {
1254 return false;
1255 }
1256 }
1257
1258 static remove(key: string): boolean {
1259 try {
1260 localStorage.removeItem(key);
1261 return true;
1262 } catch {
1263 return false;
1264 }
1265 }
1266
1267 static clear(): boolean {
1268 try {
1269 localStorage.clear();
1270 return true;
1271 } catch {
1272 return false;
1273 }
1274 }
1275}
1276"#;
1277
1278 Ok(utils_code.to_string())
1279 }
1280
1281 fn generate_index(&self) -> Result<String, Box<dyn std::error::Error>> {
1283 let index_code = format!(
1284 r#"
1285/**
1286 * AuthFramework Enhanced SDK with RBAC Support
1287 *
1288 * @version {version}
1289 * @description Comprehensive authentication and authorization client library
1290 */
1291
1292// Core client
1293export {{ {client_name}, HttpError }} from './client';
1294export type {{ ClientConfig, ApiResponse }} from './client';
1295
1296// Type definitions
1297export * from './types';
1298
1299// RBAC module
1300{rbac_export}
1301
1302// Conditional permissions
1303{conditional_export}
1304
1305// Audit logging
1306{audit_export}
1307
1308// Utilities
1309export * from './utils';
1310
1311// Main SDK class that combines all functionality
1312export class AuthFrameworkSdk {{
1313 public readonly client: {client_name};
1314 {rbac_property}
1315 {conditional_property}
1316 {audit_property}
1317
1318 constructor(config: ClientConfig) {{
1319 this.client = new {client_name}(config);
1320 {rbac_init}
1321 {conditional_init}
1322 {audit_init}
1323 }}
1324
1325 /**
1326 * Set authentication token for all requests
1327 */
1328 setAccessToken(token: string): void {{
1329 this.client.setAccessToken(token);
1330 }}
1331
1332 /**
1333 * Clear authentication token
1334 */
1335 clearAccessToken(): void {{
1336 this.client.clearAccessToken();
1337 }}
1338
1339 /**
1340 * Set API key for authentication
1341 */
1342 setApiKey(apiKey: string): void {{
1343 this.client.setApiKey(apiKey);
1344 }}
1345
1346 /**
1347 * Check if user is authenticated
1348 */
1349 isAuthenticated(): boolean {{
1350 const token = this.client.getAccessToken?.() ?? null;
1351 if (!token) {{
1352 return false;
1353 }}
1354 // Decode the JWT payload to check expiry without a full verify round-trip.
1355 try {{
1356 const [, payloadB64] = token.split('.');
1357 const json = atob(payloadB64.replace(/-/g, '+').replace(/_/g, '/'));
1358 const payload: {{ exp?: number }} = JSON.parse(json);
1359 if (payload.exp != null && Date.now() / 1000 > payload.exp) {{
1360 return false;
1361 }}
1362 }} catch {{
1363 return false;
1364 }}
1365 return true;
1366 }}
1367
1368 /**
1369 * Refresh authentication token
1370 */
1371 async refreshToken(refreshToken: string): Promise<{{ accessToken: string; refreshToken: string }}> {{
1372 const response = await this.client.post<{{ access_token: string; refresh_token: string }}>(
1373 '/auth/oauth/token/refresh',
1374 {{ refresh_token: refreshToken }},
1375 );
1376 const accessToken = response.access_token;
1377 this.client.setAccessToken(accessToken);
1378 return {{ accessToken, refreshToken: response.refresh_token }};
1379 }}
1380}}
1381
1382// Default export
1383export default AuthFrameworkSdk;
1384
1385// Create convenience function
1386export function createAuthFrameworkClient(config: ClientConfig): AuthFrameworkSdk {{
1387 return new AuthFrameworkSdk(config);
1388}}
1389"#,
1390 version = self.config.version,
1391 client_name = self.config.client_name,
1392 rbac_export = if self.config.include_rbac {
1393 "export { RbacManager } from './rbac';"
1394 } else {
1395 ""
1396 },
1397 conditional_export = if self.config.include_conditional_permissions {
1398 "export { ConditionalPermissions } from './conditional';"
1399 } else {
1400 ""
1401 },
1402 audit_export = if self.config.include_audit {
1403 "export { AuditManager } from './audit';"
1404 } else {
1405 ""
1406 },
1407 rbac_property = if self.config.include_rbac {
1408 "public readonly rbac: RbacManager;"
1409 } else {
1410 ""
1411 },
1412 conditional_property = if self.config.include_conditional_permissions {
1413 "public readonly conditional: ConditionalPermissions;"
1414 } else {
1415 ""
1416 },
1417 audit_property = if self.config.include_audit {
1418 "public readonly audit: AuditManager;"
1419 } else {
1420 ""
1421 },
1422 rbac_init = if self.config.include_rbac {
1423 "this.rbac = new RbacManager(this.client);"
1424 } else {
1425 ""
1426 },
1427 conditional_init = if self.config.include_conditional_permissions {
1428 "this.conditional = new ConditionalPermissions(this.rbac);"
1429 } else {
1430 ""
1431 },
1432 audit_init = if self.config.include_audit {
1433 "this.audit = new AuditManager(this.client);"
1434 } else {
1435 ""
1436 },
1437 );
1438
1439 Ok(index_code)
1440 }
1441
1442 fn generate_package_json(&self) -> Result<String, Box<dyn std::error::Error>> {
1444 let package_json = r#"{{
1445 "name": "@authframework/client",
1446 "version": "1.0.0",
1447 "description": "Enhanced AuthFramework client library with RBAC support",
1448 "main": "dist/index.js",
1449 "types": "dist/index.d.ts",
1450 "scripts": {{
1451 "build": "tsc",
1452 "build:watch": "tsc --watch",
1453 "test": "jest",
1454 "test:watch": "jest --watch",
1455 "lint": "eslint src/**/*.ts",
1456 "lint:fix": "eslint src/**/*.ts --fix",
1457 "clean": "rimraf dist",
1458 "prepublishOnly": "npm run clean && npm run build"
1459 }},
1460 "keywords": [
1461 "auth",
1462 "authentication",
1463 "authorization",
1464 "rbac",
1465 "oauth",
1466 "jwt",
1467 "typescript"
1468 ],
1469 "author": "AuthFramework",
1470 "license": "MIT",
1471 "dependencies": {{
1472 "uuid": "^9.0.0"
1473 }},
1474 "devDependencies": {{
1475 "@types/jest": "^29.0.0",
1476 "@types/node": "^18.0.0",
1477 "@types/uuid": "^9.0.0",
1478 "@typescript-eslint/eslint-plugin": "^5.0.0",
1479 "@typescript-eslint/parser": "^5.0.0",
1480 "eslint": "^8.0.0",
1481 "jest": "^29.0.0",
1482 "rimraf": "^3.0.0",
1483 "ts-jest": "^29.0.0",
1484 "typescript": "^4.9.0"
1485 }},
1486 "files": [
1487 "dist/**/*"
1488 ],
1489 "repository": {{
1490 "type": "git",
1491 "url": "https://github.com/authframework/client-js.git"
1492 }},
1493 "bugs": {{
1494 "url": "https://github.com/authframework/client-js/issues"
1495 }},
1496 "homepage": "https://github.com/authframework/client-js#readme"
1497}}
1498"#
1499 .to_string();
1500
1501 Ok(package_json)
1502 }
1503
1504 fn generate_readme(&self) -> Result<String, Box<dyn std::error::Error>> {
1506 let readme = r#"# AuthFramework JavaScript/TypeScript SDK
1507
1508Enhanced client library for AuthFramework with comprehensive RBAC support powered by role-system v1.0.
1509
1510## Features
1511
1512- 🔐 **Complete Authentication** - JWT, OAuth, API keys
1513- 👥 **Enterprise RBAC** - Hierarchical roles and permissions
1514- ⏰ **Conditional Permissions** - Time, location, and context-aware access control
1515- 📊 **Audit Logging** - Comprehensive activity tracking
1516- 🚀 **TypeScript Support** - Full type safety and IntelliSense
1517- 🔄 **Automatic Retries** - Built-in error handling and retry logic
1518- 📱 **Cross-Platform** - Works in browsers and Node.js
1519
1520## Installation
1521
1522```bash
1523npm install @authframework/client
1524# or
1525yarn add @authframework/client
1526```
1527
1528## Quick Start
1529
1530```typescript
1531import {{ AuthFrameworkSdk }} from '@authframework/client';
1532
1533// Initialize the client
1534const auth = new AuthFrameworkSdk({{
1535 baseUrl: 'https://your-api.example.com',
1536 accessToken: 'your-jwt-token'
1537}});
1538
1539// Check permissions
1540const canEdit = await auth.rbac.hasPermission('edit', 'documents');
1541
1542// Get user roles
1543const userRoles = await auth.rbac.getUserRoles('user123');
1544
1545// Conditional permission check
1546const canAccessAfterHours = await auth.conditional.checkPermissionWithContext(
1547 'access',
1548 'admin-panel'
1549);
1550```
1551
1552## RBAC (Role-Based Access Control)
1553
1554### Role Management
1555
1556```typescript
1557// Create a new role
1558await auth.rbac.createRole({{
1559 name: 'Editor',
1560 description: 'Can edit content',
1561 permissions: ['edit:documents', 'read:documents']
1562}});
1563
1564// Assign role to user
1565await auth.rbac.assignUserRole('user123', {{
1566 role_id: 'editor-role-id',
1567 expires_at: '2024-12-31T23:59:59Z'
1568}});
1569
1570// Check user permissions
1571const permissions = await auth.rbac.getUserRoles('user123');
1572console.log(permissions.effective_permissions);
1573```
1574
1575### Permission Checking
1576
1577```typescript
1578// Simple permission check
1579const hasPermission = await auth.rbac.hasPermission('delete', 'documents');
1580
1581// Detailed permission check with context
1582const result = await auth.rbac.checkPermission({{
1583 action: 'access',
1584 resource: 'admin-panel',
1585 context: {{
1586 ip_address: '192.168.1.100',
1587 time_of_day: 'business_hours'
1588 }}
1589}});
1590
1591console.log(result.data?.granted); // boolean
1592console.log(result.data?.reason); // explanation
1593```
1594
1595## Conditional Permissions
1596
1597Context-aware permissions based on time, location, device, and custom attributes.
1598
1599```typescript
1600// Check permission with environmental context
1601const canAccess = await auth.conditional.checkPermissionWithContext(
1602 'access',
1603 'sensitive-data'
1604);
1605
1606// Build custom context
1607const context = auth.conditional.buildContext();
1608console.log(context.time_of_day); // 'business_hours' | 'after_hours' | 'weekend'
1609console.log(context.device_type); // 'desktop' | 'mobile' | 'tablet'
1610console.log(context.connection_type); // 'direct' | 'vpn' | 'proxy'
1611
1612// High-security context for sensitive operations
1613const highSecContext = auth.conditional.createHighSecurityContext();
1614const result = await auth.rbac.checkPermission({{
1615 action: 'delete',
1616 resource: 'user-accounts',
1617 context: highSecContext
1618}});
1619```
1620
1621## Audit Logging
1622
1623Comprehensive activity tracking and analysis.
1624
1625```typescript
1626// Get audit logs
1627const logs = await auth.audit.getAuditLogs({{
1628 user_id: 'user123',
1629 start_time: '2024-01-01T00:00:00Z',
1630 end_time: '2024-01-31T23:59:59Z',
1631 page: 1,
1632 per_page: 50
1633}});
1634
1635// Get recent activity
1636const recent = await auth.audit.getRecentAuditLogs(24); // Last 24 hours
1637
1638// Export audit data
1639const csvData = await auth.audit.exportAuditLogs({{
1640 action: 'login',
1641 start_time: '2024-01-01T00:00:00Z'
1642}});
1643
1644// Real-time monitoring
1645const stopMonitoring = auth.audit.monitorAuditEvents(
1646 (entry) => console.log('New audit entry:', entry),
1647 {{ action: 'permission_check' }}
1648);
1649
1650// Stop monitoring when done
1651stopMonitoring();
1652```
1653
1654## Role Elevation
1655
1656Temporary privilege escalation for administrative tasks.
1657
1658```typescript
1659// Request elevated permissions
1660await auth.rbac.elevateRole({{
1661 target_role: 'admin',
1662 duration_minutes: 30,
1663 justification: 'Emergency system maintenance'
1664}});
1665
1666// Check if user has elevated permissions
1667const hasElevated = await auth.rbac.hasPermission('elevated', 'admin');
1668```
1669
1670## Advanced Usage
1671
1672### Bulk Operations
1673
1674```typescript
1675// Bulk role assignment
1676await auth.rbac.bulkAssignRoles({{
1677 assignments: [
1678 {{ user_id: 'user1', role_id: 'editor' }},
1679 {{ user_id: 'user2', role_id: 'viewer' }},
1680 {{ user_id: 'user3', role_id: 'admin', expires_at: '2024-12-31T23:59:59Z' }}
1681 ]
1682}});
1683```
1684
1685### Error Handling
1686
1687```typescript
1688import {{ HttpError, isAuthError, isRateLimitError }} from '@authframework/client';
1689
1690try {{
1691 await auth.rbac.createRole(roleData);
1692}} catch (error) {{
1693 if (error instanceof HttpError) {{
1694 if (isAuthError(error)) {{
1695 // Handle authentication error
1696 console.log('Authentication required');
1697 }} else if (isRateLimitError(error)) {{
1698 // Handle rate limit
1699 console.log('Rate limit exceeded');
1700 }} else {{
1701 console.log('HTTP error:', error.status, error.statusText);
1702 }}
1703 }}
1704}}
1705```
1706
1707### Custom Configuration
1708
1709```typescript
1710const auth = new AuthFrameworkSdk({{
1711 baseUrl: 'https://api.example.com',
1712 accessToken: 'your-token',
1713 timeout: 30000, // 30 second timeout
1714 retryAttempts: 3, // Retry failed requests 3 times
1715}});
1716
1717// Dynamic token updates
1718auth.setAccessToken('new-token');
1719
1720// API key authentication
1721auth.setApiKey('your-api-key');
1722```
1723
1724## Type Safety
1725
1726Full TypeScript support with comprehensive type definitions:
1727
1728```typescript
1729import type {{
1730 Role,
1731 Permission,
1732 UserRolesResponse,
1733 PermissionCheckResponse,
1734 ConditionalContext
1735}} from '@authframework/client';
1736
1737const role: Role = {{
1738 id: 'role-123',
1739 name: 'Editor',
1740 description: 'Content editor role',
1741 parent_id: 'base-user',
1742 permissions: ['edit:content', 'read:content'],
1743 created_at: '2024-01-01T00:00:00Z',
1744 updated_at: '2024-01-01T00:00:00Z'
1745}};
1746```
1747
1748## Browser Support
1749
1750- Chrome 60+
1751- Firefox 60+
1752- Safari 12+
1753- Edge 79+
1754
1755## Node.js Support
1756
1757- Node.js 14+
1758
1759## License
1760
1761MIT License - see LICENSE file for details.
1762
1763## Support
1764
1765- 📖 [Documentation](https://docs.authframework.com)
1766- 🐛 [Issue Tracker](https://github.com/authframework/client-js/issues)
1767- 💬 [Discord Community](https://discord.gg/authframework)
1768"#.to_string();
1769
1770 Ok(readme)
1771 }
1772}
1773
1774#[cfg(test)]
1775mod tests {
1776 use super::*;
1777
1778 #[test]
1779 fn test_sdk_generation() {
1780 let config = EnhancedSdkConfig::default();
1781 let generator = JsSdkGenerator::new(config);
1782
1783 let result = generator.generate_sdk();
1784 assert!(result.is_ok());
1785
1786 let files = result.unwrap();
1787 assert!(files.contains_key("client.ts"));
1788 assert!(files.contains_key("types.ts"));
1789 assert!(files.contains_key("rbac.ts"));
1790 assert!(files.contains_key("index.ts"));
1791 assert!(files.contains_key("package.json"));
1792 }
1793
1794 #[test]
1795 fn test_typescript_generation() {
1796 let config = EnhancedSdkConfig {
1797 typescript: true,
1798 include_rbac: true,
1799 include_conditional_permissions: true,
1800 include_audit: true,
1801 ..Default::default()
1802 };
1803
1804 let generator = JsSdkGenerator::new(config);
1805 let files = generator.generate_sdk().unwrap();
1806
1807 assert!(files.contains_key("types.ts"));
1809 assert!(files.contains_key("rbac.ts"));
1810 assert!(files.contains_key("conditional.ts"));
1811 assert!(files.contains_key("audit.ts"));
1812 }
1813}