Skip to content

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

types.ts
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 classes
export 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 interface
export 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

api-implementation.ts
import { User, SystemStats, UserManagementAPI, ValidationError, UserNotFoundError, DatabaseError } from './types'
// Mock database
const users = new Map<string, User>()
const systemSettings = {
defaultTheme: 'light' as const,
allowRegistration: true,
maxUsers: 1000
}
let currentUser: User | null = null
let 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
error.expectedFormat = '[email protected]'
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

client.ts
import { RPCChannel, WorkerParentIO } from 'kkrpc'
import { UserManagementAPI, ValidationError, UserNotFoundError, DatabaseError } from './types'
// Set up RPC connection
const 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 demonstration
demonstrateFeatures()

Worker Implementation

api-worker.ts
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 calls
console.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.