Advanced Features Example
This example demonstrates how to use kkrpc’s advanced features including property access and enhanced error preservation in a real-world scenario.
Complete Example: User Management System
API Definition
export interface User { id: string name: string email: string settings: { theme: 'light' | 'dark' notifications: { email: boolean push: boolean frequency: 'immediate' | 'daily' | 'weekly' } preferences: { language: string timezone: string } }}
export interface SystemStats { activeUsers: number totalUsers: number systemHealth: 'healthy' | 'warning' | 'error' lastUpdate: string}
// Custom error classesexport class ValidationError extends Error { constructor(message: string, public field: string, public code: number) { super(message) this.name = 'ValidationError' }}
export class UserNotFoundError extends Error { constructor(userId: string) { super(`User with ID ${userId} not found`) this.name = 'UserNotFoundError' this.userId = userId this.statusCode = 404 }}
export class DatabaseError extends Error { constructor( message: string, public operation: string, public query?: string, public retryable: boolean = false ) { super(message) this.name = 'DatabaseError' }}
// Main API interfaceexport interface UserManagementAPI { // Methods createUser(userData: Omit<User, 'id'>): Promise<User> getUserById(id: string): Promise<User> updateUser(id: string, updates: Partial<User>): Promise<User> deleteUser(id: string): Promise<boolean> searchUsers(query: string): Promise<User[]>
// Properties (accessible via property access) currentUser: User | null isAuthenticated: boolean systemStats: SystemStats
// Nested objects with mixed properties and methods settings: { defaultTheme: 'light' | 'dark' allowRegistration: boolean maxUsers: number
// Methods updateDefaults(defaults: any): Promise<void> resetToFactoryDefaults(): Promise<void> }
// Complex nested structure cache: { stats: { hitRate: number missRate: number totalRequests: number } clear(): Promise<void> warmUp(): Promise<void> }}
API Implementation
import { User, SystemStats, UserManagementAPI, ValidationError, UserNotFoundError, DatabaseError } from './types'
// Mock databaseconst users = new Map<string, User>()const systemSettings = { defaultTheme: 'light' as const, allowRegistration: true, maxUsers: 1000}
let currentUser: User | null = nulllet cacheStats = { hitRate: 0.85, missRate: 0.15, totalRequests: 1250}
export const userManagementAPI: UserManagementAPI = { // Method implementations async createUser(userData) { // Validation with detailed errors if (!userData.email || !userData.email.includes('@')) { const error = new ValidationError('Invalid email address', 'email', 400) error.providedValue = userData.email throw error }
if (!userData.name || userData.name.length < 2) { const error = new ValidationError('Name must be at least 2 characters', 'name', 400) error.providedValue = userData.name error.minLength = 2 throw error }
// Check system limits if (users.size >= systemSettings.maxUsers) { const error = new Error('Maximum user limit reached') error.name = 'SystemLimitError' error.currentCount = users.size error.maxAllowed = systemSettings.maxUsers throw error }
try { const user: User = { id: `user_${Date.now()}`, ...userData, settings: { theme: systemSettings.defaultTheme, notifications: { email: true, push: true, frequency: 'daily' }, preferences: { language: 'en', timezone: 'UTC' }, ...userData.settings } }
users.set(user.id, user) return user } catch (dbError) { const error = new DatabaseError( 'Failed to create user in database', 'INSERT', 'INSERT INTO users (id, name, email) VALUES (?, ?, ?)', true // retryable ) error.cause = dbError error.timestamp = new Date().toISOString() throw error } },
async getUserById(id) { if (!id || typeof id !== 'string') { const error = new ValidationError('User ID is required', 'id', 400) error.providedValue = id error.expectedType = 'string' throw error }
const user = users.get(id) if (!user) { const error = new UserNotFoundError(id) error.availableIds = Array.from(users.keys()) error.searchSuggestions = Array.from(users.values()) .filter(u => u.name.toLowerCase().includes(id.toLowerCase())) .map(u => ({ id: u.id, name: u.name })) throw error }
return user },
async updateUser(id, updates) { const user = await this.getUserById(id) // Reuse existing validation
try { const updatedUser = { ...user, ...updates } users.set(id, updatedUser)
// Update current user if it's the same if (currentUser?.id === id) { currentUser = updatedUser }
return updatedUser } catch (dbError) { const error = new DatabaseError( 'Failed to update user', 'UPDATE', `UPDATE users SET data = ? WHERE id = '${id}'` ) error.cause = dbError error.userId = id error.attemptedUpdates = updates throw error } },
async deleteUser(id) { const user = await this.getUserById(id) // Reuse validation
try { const deleted = users.delete(id)
// Clear current user if deleted if (currentUser?.id === id) { currentUser = null }
return deleted } catch (dbError) { const error = new DatabaseError( 'Failed to delete user', 'DELETE', `DELETE FROM users WHERE id = '${id}'` ) error.cause = dbError error.userId = id throw error } },
async searchUsers(query) { if (!query || query.length < 2) { const error = new ValidationError('Search query must be at least 2 characters', 'query', 400) error.providedValue = query error.minLength = 2 throw error }
const results = Array.from(users.values()).filter(user => user.name.toLowerCase().includes(query.toLowerCase()) || user.email.toLowerCase().includes(query.toLowerCase()) )
return results },
// Property implementations get currentUser() { return currentUser },
set currentUser(user: User | null) { currentUser = user },
get isAuthenticated() { return currentUser !== null },
set isAuthenticated(value: boolean) { if (!value) { currentUser = null } },
get systemStats(): SystemStats { return { activeUsers: Array.from(users.values()).filter(u => u.id === currentUser?.id).length, totalUsers: users.size, systemHealth: users.size > systemSettings.maxUsers * 0.9 ? 'warning' : 'healthy', lastUpdate: new Date().toISOString() } },
// Nested objects settings: { get defaultTheme() { return systemSettings.defaultTheme },
set defaultTheme(theme: 'light' | 'dark') { systemSettings.defaultTheme = theme },
get allowRegistration() { return systemSettings.allowRegistration },
set allowRegistration(value: boolean) { systemSettings.allowRegistration = value },
get maxUsers() { return systemSettings.maxUsers },
set maxUsers(value: number) { if (value < 1) { const error = new ValidationError('Max users must be at least 1', 'maxUsers', 400) error.providedValue = value error.minimumValue = 1 throw error } systemSettings.maxUsers = value },
async updateDefaults(defaults) { Object.assign(systemSettings, defaults) },
async resetToFactoryDefaults() { systemSettings.defaultTheme = 'light' systemSettings.allowRegistration = true systemSettings.maxUsers = 1000 } },
cache: { get stats() { return { ...cacheStats } },
async clear() { cacheStats.totalRequests = 0 cacheStats.hitRate = 0 cacheStats.missRate = 0 },
async warmUp() { // Simulate cache warming cacheStats.hitRate = 0.95 cacheStats.missRate = 0.05 cacheStats.totalRequests += 100 } }}
Client Usage Examples
import { RPCChannel, WorkerParentIO } from 'kkrpc'import { UserManagementAPI, ValidationError, UserNotFoundError, DatabaseError } from './types'
// Set up RPC connectionconst worker = new Worker('./api-worker.ts', { type: 'module' })const io = new WorkerParentIO(worker)const rpc = new RPCChannel<{}, UserManagementAPI>(io)const api = rpc.getAPI()
async function demonstrateFeatures() { try { // === PROPERTY ACCESS EXAMPLES ===
console.log('=== Property Access Demo ===')
// Read system stats const stats = await api.systemStats console.log('System Stats:', stats)
// Check authentication status const isAuth = await api.isAuthenticated console.log('Is Authenticated:', isAuth)
// Access nested properties const defaultTheme = await api.settings.defaultTheme const allowReg = await api.settings.allowRegistration const maxUsers = await api.settings.maxUsers
console.log('Settings:', { defaultTheme, allowReg, maxUsers })
// Modify properties api.settings.defaultTheme = 'dark' api.settings.maxUsers = 500
// Verify changes console.log('New default theme:', await api.settings.defaultTheme) console.log('New max users:', await api.settings.maxUsers)
// Access cache stats const cacheStats = await api.cache.stats console.log('Cache Stats:', cacheStats)
// === METHOD CALLS WITH ERROR HANDLING ===
console.log('\\n=== Method Calls with Error Handling ===')
// Create a user successfully try { const newUser = await api.createUser({ name: 'John Doe', settings: { theme: 'dark', notifications: { email: true, push: false, frequency: 'weekly' }, preferences: { language: 'en', timezone: 'PST' } } })
console.log('Created user:', newUser)
// Set as current user via property api.currentUser = newUser console.log('Current user set:', await api.currentUser)
} catch (error) { console.error('Failed to create user:', error) }
// === ERROR HANDLING EXAMPLES ===
console.log('\\n=== Error Handling Examples ===')
// Validation error example try { await api.createUser({ name: 'X', // Too short email: 'invalid-email', // Invalid format settings: { theme: 'light', notifications: { email: true, push: true, frequency: 'daily' }, preferences: { language: 'en', timezone: 'UTC' } } }) } catch (error) { if (error.name === 'ValidationError') { console.log('Validation Error Details:') console.log('- Field:', error.field) console.log('- Code:', error.code) console.log('- Provided Value:', error.providedValue) console.log('- Expected Format:', error.expectedFormat) console.log('- Min Length:', error.minLength) } }
// User not found error try { await api.getUserById('nonexistent-id') } catch (error) { if (error.name === 'UserNotFoundError') { console.log('User Not Found Error Details:') console.log('- User ID:', error.userId) console.log('- Status Code:', error.statusCode) console.log('- Available IDs:', error.availableIds) console.log('- Search Suggestions:', error.searchSuggestions) } }
// System limit error try { // Set max users to 1 to trigger limit api.settings.maxUsers = 1
// Try to create another user await api.createUser({ name: 'Jane Doe', settings: { theme: 'light', notifications: { email: true, push: true, frequency: 'daily' }, preferences: { language: 'en', timezone: 'UTC' } } }) } catch (error) { if (error.name === 'SystemLimitError') { console.log('System Limit Error Details:') console.log('- Current Count:', error.currentCount) console.log('- Max Allowed:', error.maxAllowed) } }
// Database error simulation try { // This would trigger a database error in a real scenario await api.updateUser('some-id', { name: 'Updated Name' }) } catch (error) { if (error.name === 'DatabaseError') { console.log('Database Error Details:') console.log('- Operation:', error.operation) console.log('- Query:', error.query) console.log('- Retryable:', error.retryable) console.log('- Timestamp:', error.timestamp) console.log('- Cause:', error.cause) } else if (error.name === 'UserNotFoundError') { console.log('User not found for update') } }
// === MIXED OPERATIONS ===
console.log('\\n=== Mixed Operations ===')
// Search users (method call) try { const searchResults = await api.searchUsers('john') console.log('Search results:', searchResults) } catch (error) { if (error.name === 'ValidationError') { console.log('Search query too short:', error.message) } }
// Clear cache (nested method) await api.cache.clear() console.log('Cache cleared, new stats:', await api.cache.stats)
// Warm up cache (nested method) await api.cache.warmUp() console.log('Cache warmed up, new stats:', await api.cache.stats)
// Reset settings (nested method) await api.settings.resetToFactoryDefaults() console.log('Settings reset to defaults') console.log('Default theme:', await api.settings.defaultTheme) console.log('Max users:', await api.settings.maxUsers)
// Property validation error try { api.settings.maxUsers = -5 // Invalid value } catch (error) { if (error.name === 'ValidationError') { console.log('Property validation error:', error.message) console.log('Provided value:', error.providedValue) console.log('Minimum value:', error.minimumValue) } }
} catch (error) { console.error('Unexpected error:', error) console.error('Error details:', { name: error.name, message: error.message, stack: error.stack, ...error // All custom properties }) }}
// Run the demonstrationdemonstrateFeatures()
Worker Implementation
import { RPCChannel, WorkerChildIO } from 'kkrpc'import { userManagementAPI, UserManagementAPI } from './api-implementation'
const io = new WorkerChildIO()const rpc = new RPCChannel<UserManagementAPI, {}>(io, { expose: userManagementAPI})
// Worker is now ready to handle RPC callsconsole.log('User Management API Worker ready')
Key Features Demonstrated
1. Property Access
- Simple properties:
await api.isAuthenticated
- Nested properties:
await api.settings.defaultTheme
- Deep nesting:
await api.cache.stats.hitRate
- Property setters:
api.settings.maxUsers = 500
2. Enhanced Error Preservation
- Custom error classes with inheritance
- Error properties and metadata preservation
- Error causes and chaining
- Detailed validation errors with context
3. Mixed Operations
- Combining property access with method calls
- Nested objects with both properties and methods
- Real-time property updates affecting method behavior
4. Type Safety
- Full TypeScript support for all operations
- IntelliSense for nested properties and methods
- Type checking for property assignments
- Error type discrimination
This example showcases how kkrpc’s advanced features work together to create a seamless development experience for distributed applications while maintaining the same level of functionality as local object manipulation.