web-app/src/core/di-container.ts
padreug 9ed674d0f3 Adds expense tracking module
Adds a new module for tracking user expenses.

The module includes:
- Configuration settings for the LNbits API endpoint and timeouts.
- An ExpensesAPI service for fetching accounts and submitting expense entries.
- A UI component for adding expenses, including account selection and form input.
- Dependency injection for the ExpensesAPI service.

This allows users to submit expense entries with account selection and reference data, which will be linked to their wallet.
2025-11-07 16:21:59 +01:00

179 lines
No EOL
4.2 KiB
TypeScript

import type { DIContainer, ServiceToken } from './types'
export type { ServiceToken } from './types'
interface ServiceRegistration {
service: any
scope: 'singleton' | 'transient'
instance?: any
}
/**
* Dependency Injection Container
* Manages service registration and injection across modules
*/
export class DIContainerImpl implements DIContainer {
private services = new Map<ServiceToken, ServiceRegistration>()
/**
* Register a service in the container
*/
provide<T>(token: ServiceToken, service: T, scope: 'singleton' | 'transient' = 'singleton'): void {
this.services.set(token, {
service,
scope,
instance: scope === 'singleton' ? service : undefined
})
}
/**
* Inject a service from the container
*/
inject<T>(token: ServiceToken): T | undefined {
const registration = this.services.get(token)
if (!registration) {
return undefined
}
if (registration.scope === 'singleton') {
return registration.instance as T
}
// For transient services, create new instance
// Note: This assumes the service is a constructor function
if (typeof registration.service === 'function') {
try {
return new registration.service() as T
} catch (error) {
console.error(`Error creating transient service for token ${String(token)}:`, error)
return undefined
}
}
return registration.service as T
}
/**
* Remove a service from the container
*/
remove(token: ServiceToken): boolean {
return this.services.delete(token)
}
/**
* Clear all services
*/
clear(): void {
this.services.clear()
}
/**
* Get all registered service tokens
*/
getRegisteredTokens(): ServiceToken[] {
return Array.from(this.services.keys())
}
/**
* Check if a service is registered
*/
has(token: ServiceToken): boolean {
return this.services.has(token)
}
/**
* Get service registration info
*/
getServiceInfo(token: ServiceToken): { scope: string; hasInstance: boolean } | undefined {
const registration = this.services.get(token)
if (!registration) {
return undefined
}
return {
scope: registration.scope,
hasInstance: registration.instance !== undefined
}
}
}
// Global DI container instance
export const container = new DIContainerImpl()
// Service token constants
export const SERVICE_TOKENS = {
// Core services
EVENT_BUS: Symbol('eventBus'),
ROUTER: Symbol('router'),
// Nostr services
RELAY_HUB: Symbol('relayHub'),
// Auth services
AUTH_SERVICE: Symbol('authService'),
// Payment services
PAYMENT_SERVICE: Symbol('paymentService'),
// Visibility services
VISIBILITY_SERVICE: Symbol('visibilityService'),
// Storage services
STORAGE_SERVICE: Symbol('storageService'),
// Toast services
TOAST_SERVICE: Symbol('toastService'),
// Market services
MARKET_STORE: Symbol('marketStore'),
PAYMENT_MONITOR: Symbol('paymentMonitor'),
// Chat services
CHAT_SERVICE: Symbol('chatService'),
// Feed services
FEED_SERVICE: Symbol('feedService'),
PROFILE_SERVICE: Symbol('profileService'),
REACTION_SERVICE: Symbol('reactionService'),
SCHEDULED_EVENT_SERVICE: Symbol('scheduledEventService'),
// Nostr metadata services
NOSTR_METADATA_SERVICE: Symbol('nostrMetadataService'),
// Events services
EVENTS_SERVICE: Symbol('eventsService'),
// Invoice services
INVOICE_SERVICE: Symbol('invoiceService'),
// Nostrmarket services
NOSTRMARKET_SERVICE: Symbol('nostrmarketService'),
NOSTRMARKET_API: Symbol('nostrmarketAPI'),
// Wallet services
WALLET_SERVICE: Symbol('walletService'),
WALLET_WEBSOCKET_SERVICE: Symbol('walletWebSocketService'),
// API services
LNBITS_API: Symbol('lnbitsAPI'),
// Image upload services
IMAGE_UPLOAD_SERVICE: Symbol('imageUploadService'),
// Expenses services
EXPENSES_API: Symbol('expensesAPI'),
} as const
// Type-safe injection helpers
export function injectService<T>(token: ServiceToken): T {
const service = container.inject<T>(token)
if (!service) {
throw new Error(`Service not found for token: ${String(token)}`)
}
return service
}
export function tryInjectService<T>(token: ServiceToken): T | undefined {
return container.inject<T>(token)
}