- Reorganize all markdown documentation into structured docs/ folder - Create 7 main documentation categories (00-overview through 06-deployment) - Add comprehensive index files for each category with cross-linking - Implement Obsidian-compatible [[link]] syntax throughout - Move legacy/deprecated documentation to archive folder - Establish documentation standards and maintenance guidelines - Provide complete coverage of modular architecture, services, and deployment - Enable better navigation and discoverability for developers and contributors 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
15 KiB
⚙️ Core Services Overview
Shared infrastructure services providing foundational functionality across all modules with reactive architecture, dependency injection, and lifecycle management.
Table of Contents
- #Service Architecture
- #Available Services
- #Service Lifecycle
- #Dependency Injection
- #Service Development
- #Testing Services
Service Architecture
BaseService Foundation
All core services extend the BaseService abstract class which provides:
- Reactive State Management - Integration with Vue's reactivity system
- Lifecycle Management - Standardized initialization and disposal
- Error Handling - Consistent error patterns across services
- Type Safety - Full TypeScript support with strict typing
abstract class BaseService {
protected isInitialized = ref(false)
protected isDisposed = ref(false)
abstract initialize(): Promise<void>
abstract dispose(): Promise<void>
// Reactive state helpers
protected createReactiveState<T>(initialValue: T): Ref<T>
protected createComputedState<T>(getter: () => T): ComputedRef<T>
}
Service Registration Pattern
Services are registered in the dependency injection container during module installation:
// Service registration (in base module)
container.provide(SERVICE_TOKENS.RELAY_HUB, relayHub)
container.provide(SERVICE_TOKENS.AUTH_SERVICE, authService)
container.provide(SERVICE_TOKENS.STORAGE_SERVICE, storageService)
// Service consumption (anywhere in app)
const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB)
const auth = injectService(SERVICE_TOKENS.AUTH_SERVICE)
Available Services
AuthService 🔐
Purpose: User authentication and identity management
Location: src/modules/base/auth/auth-service.ts
Token: SERVICE_TOKENS.AUTH_SERVICE
Key Features:
- Key Management - Secure generation, import, and storage of Nostr keys
- User Sessions - Persistent authentication with encrypted storage
- Profile Management - User profile creation and updates
- Security - Client-side key handling with no server storage
Reactive State:
interface AuthService {
user: Ref<NostrUser | null> // Current authenticated user
isAuthenticated: ComputedRef<boolean> // Authentication status
isLoading: Ref<boolean> // Loading state
loginError: Ref<string | null> // Login error message
}
Key Methods:
generateKeyPair()- Create new Nostr key pairloginWithPrivateKey(privateKey: string)- Authenticate with existing keylogout()- Clear session and user dataupdateProfile(profile: UserMetadata)- Update user profile
See: authentication
RelayHub 🌐
Purpose: Centralized Nostr relay connection management
Location: src/modules/base/nostr/relay-hub.ts
Token: SERVICE_TOKENS.RELAY_HUB
Key Features:
- Connection Management - Automatic connection, reconnection, and failover
- Event Publishing - Reliable event publishing across multiple relays
- Subscription Management - Efficient event subscriptions with deduplication
- Performance Monitoring - Relay latency and success rate tracking
Reactive State:
interface RelayHub {
connectedRelays: Ref<string[]> // Currently connected relays
connectionStatus: ComputedRef<ConnectionStatus> // Overall connection status
relayStats: Ref<Map<string, RelayStats>> // Per-relay statistics
isConnecting: Ref<boolean> // Connection in progress
}
Key Methods:
connect(relays: string[])- Connect to relay URLspublishEvent(event: NostrEvent)- Publish event to all connected relayssubscribe(filters: Filter[], callback: EventCallback)- Subscribe to eventsgetRelayInfo(url: string)- Get relay connection information
See: ../01-architecture/relay-hub
StorageService 💾
Purpose: User-scoped local storage operations
Location: src/core/services/StorageService.ts
Token: SERVICE_TOKENS.STORAGE_SERVICE
Key Features:
- User-Scoped Storage - Automatic key prefixing per authenticated user
- Type-Safe Operations - Strongly typed get/set operations with JSON serialization
- Reactive Updates - Optional reactive storage with Vue refs
- Migration Support - Data migration between storage schema versions
Key Methods:
interface StorageService {
setUserData<T>(key: string, data: T): void
getUserData<T>(key: string, defaultValue?: T): T | undefined
removeUserData(key: string): void
clearUserData(): void
// Reactive variants
getReactiveUserData<T>(key: string, defaultValue: T): Ref<T>
setReactiveUserData<T>(key: string, ref: Ref<T>): void
}
Storage Patterns:
- User-specific keys:
user:{pubkey}:settings - Global application keys:
app:theme - Module-specific keys:
user:{pubkey}:chat:contacts
See: storage-service
ToastService 📢
Purpose: Application-wide notifications and user feedback
Location: src/core/services/ToastService.ts
Token: SERVICE_TOKENS.TOAST_SERVICE
Key Features:
- Context-Specific Methods - Pre-configured toasts for common scenarios
- Consistent Messaging - Standardized success, error, and info messages
- Accessibility - Screen reader compatible notifications
- Customizable - Support for custom toast content and actions
Organized by Context:
interface ToastService {
// Authentication context
auth: {
loginSuccess(): void
loginError(error?: string): void
logoutSuccess(): void
keyGenerated(): void
}
// Payment context
payment: {
invoiceCreated(): void
paymentReceived(): void
paymentFailed(error?: string): void
}
// Clipboard operations
clipboard: {
copied(item?: string): void
copyFailed(): void
}
// General operations
operation: {
success(message: string): void
error(message: string): void
info(message: string): void
}
}
See: toast-service
EventBus 📡
Purpose: Inter-module communication and event coordination
Location: src/core/services/EventBus.ts
Token: SERVICE_TOKENS.EVENT_BUS
Key Features:
- Type-Safe Events - Strongly typed event payloads
- Module Isolation - Clean communication between modules
- Event Namespacing - Organized event names by domain
- Subscription Management - Easy subscribe/unsubscribe patterns
Event Categories:
interface EventBusEvents {
// User events
'user:authenticated': { userId: string, profile: UserMetadata }
'user:profile-updated': { userId: string, changes: Partial<UserMetadata> }
'user:logout': { userId: string }
// Chat events
'chat:message-received': { messageId: string, from: string, content: string }
'chat:typing-start': { from: string, chatId: string }
// Payment events
'payment:invoice-created': { invoiceId: string, amount: number }
'payment:received': { invoiceId: string, amount: number }
// Relay events
'relay:connected': { url: string }
'relay:disconnected': { url: string, reason?: string }
}
See: ../01-architecture/event-bus
Service Lifecycle
Initialization Phase
Services are initialized in dependency order during application startup:
// 1. Base services (no dependencies)
await authService.initialize()
await storageService.initialize()
// 2. Infrastructure services (depend on base services)
await relayHub.initialize()
await toastService.initialize()
// 3. Feature services (depend on infrastructure)
await chatService.initialize()
await eventsService.initialize()
Service Dependencies
Services declare their dependencies through constructor injection:
class ChatService extends BaseService {
constructor(
private auth = injectService(SERVICE_TOKENS.AUTH_SERVICE),
private relayHub = injectService(SERVICE_TOKENS.RELAY_HUB),
private storage = injectService(SERVICE_TOKENS.STORAGE_SERVICE),
private eventBus = injectService(SERVICE_TOKENS.EVENT_BUS)
) {
super()
}
}
Disposal Phase
Services are disposed in reverse dependency order during application shutdown:
async dispose(): Promise<void> {
// Clean up subscriptions
this.subscriptions.forEach(sub => sub.close())
// Save persistent state
await this.storage.setUserData('chat:messages', this.messages.value)
// Mark as disposed
this.isDisposed.value = true
}
Dependency Injection
Service Tokens
Type-safe service tokens prevent runtime errors and enable proper TypeScript inference:
export const SERVICE_TOKENS = {
AUTH_SERVICE: Symbol('AUTH_SERVICE') as InjectionKey<AuthService>,
RELAY_HUB: Symbol('RELAY_HUB') as InjectionKey<RelayHub>,
STORAGE_SERVICE: Symbol('STORAGE_SERVICE') as InjectionKey<StorageService>,
TOAST_SERVICE: Symbol('TOAST_SERVICE') as InjectionKey<ToastService>,
} as const
Service Registration
Services are registered during module installation:
// In base module installation
export async function installBaseModule(app: App) {
// Create service instances
const authService = new AuthService()
const relayHub = new RelayHub()
const storageService = new StorageService()
// Register in container
container.provide(SERVICE_TOKENS.AUTH_SERVICE, authService)
container.provide(SERVICE_TOKENS.RELAY_HUB, relayHub)
container.provide(SERVICE_TOKENS.STORAGE_SERVICE, storageService)
// Initialize services
await authService.initialize()
await relayHub.initialize()
}
Service Consumption
Services are injected where needed using type-safe injection:
// In composables
export function useAuth() {
const authService = injectService(SERVICE_TOKENS.AUTH_SERVICE)
return {
user: authService.user,
login: authService.login,
logout: authService.logout
}
}
// In components
<script setup>
const toast = injectService(SERVICE_TOKENS.TOAST_SERVICE)
const handleSuccess = () => toast.operation.success('Action completed!')
</script>
Service Development
Creating a New Service
1. Service Class Implementation
// src/core/services/MyService.ts
export class MyService extends BaseService {
// Reactive state
private readonly _data = ref<MyData[]>([])
private readonly _isLoading = ref(false)
// Public readonly access to state
public readonly data = readonly(this._data)
public readonly isLoading = readonly(this._isLoading)
constructor(
private dependency = injectService(SERVICE_TOKENS.DEPENDENCY)
) {
super()
}
async initialize(): Promise<void> {
// Initialization logic
await this.loadInitialData()
this.isInitialized.value = true
}
async dispose(): Promise<void> {
// Cleanup logic
this._data.value = []
this.isDisposed.value = true
}
// Public methods
async createItem(item: CreateItemRequest): Promise<MyData> {
this._isLoading.value = true
try {
const newItem = await this.dependency.create(item)
this._data.value.push(newItem)
return newItem
} finally {
this._isLoading.value = false
}
}
}
2. Service Token Registration
// Add to SERVICE_TOKENS
export const SERVICE_TOKENS = {
// ... existing tokens
MY_SERVICE: Symbol('MY_SERVICE') as InjectionKey<MyService>,
} as const
3. Service Registration in Module
// In module installation
const myService = new MyService()
container.provide(SERVICE_TOKENS.MY_SERVICE, myService)
await myService.initialize()
Service Best Practices
Reactive State Management
- Use
ref()for mutable state,readonly()for public access - Provide computed properties for derived state
- Use
watch()andwatchEffect()for side effects
Error Handling
- Throw descriptive errors with proper types
- Use try/catch blocks with proper cleanup
- Log errors appropriately for debugging
Performance Optimization
- Implement proper subscription cleanup in
dispose() - Use debouncing for frequent operations
- Cache expensive computations with
computed()
Testing Services
Service Unit Tests
// tests/unit/services/MyService.test.ts
describe('MyService', () => {
let service: MyService
let mockDependency: MockType<DependencyService>
beforeEach(() => {
// Setup mocks
mockDependency = createMockService()
// Create service with mocks
service = new MyService(mockDependency)
})
afterEach(async () => {
await service.dispose()
})
it('should initialize correctly', async () => {
await service.initialize()
expect(service.isInitialized.value).toBe(true)
expect(service.data.value).toEqual([])
})
it('should create items', async () => {
await service.initialize()
const item = await service.createItem({ name: 'test' })
expect(service.data.value).toContain(item)
expect(mockDependency.create).toHaveBeenCalledWith({ name: 'test' })
})
})
Integration Tests
// tests/integration/services/ServiceIntegration.test.ts
describe('Service Integration', () => {
let container: DIContainer
beforeEach(async () => {
container = createTestContainer()
await installTestServices(container)
})
it('should handle cross-service communication', async () => {
const authService = container.get(SERVICE_TOKENS.AUTH_SERVICE)
const chatService = container.get(SERVICE_TOKENS.CHAT_SERVICE)
await authService.login('test-key')
const message = await chatService.sendMessage('Hello')
expect(message.author).toBe(authService.user.value?.pubkey)
})
})
See Also
Service Documentation
- authentication - User identity and session management
- storage-service - User-scoped data persistence
- toast-service - User notifications and feedback
- visibility-service - Dynamic UI component control
Architecture References
- ../01-architecture/dependency-injection - DI container system
- ../01-architecture/event-bus - Inter-service messaging
- ../02-modules/index - How services integrate with modules
- ../04-development/testing - Service testing patterns
Tags: #services #architecture #dependency-injection #reactive-state
Last Updated: 2025-09-06
Author: Development Team