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.
179 lines
No EOL
4.2 KiB
TypeScript
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)
|
|
} |