import type { DeviceRecord } from '../../domain/device/device_record.js' import type { ReferenceProvider, ResolvedReference, InformContext, ProviderConfig, } from './types.js' import { TRIGGER_EVENT_MAP } from './types.js' import { loadReferencesConfig } from './config_loader.js' import { WassupProvider } from './providers/wassup_provider.js' export class ReferenceEngine { private providers: ReferenceProvider[] = [] private configs: ProviderConfig[] = [] constructor() { this.loadProviders() } private loadProviders(): void { const config = loadReferencesConfig() this.configs = config.providers for (const providerConfig of config.providers) { if (!providerConfig.enabled) continue const provider = this.createProvider(providerConfig) if (provider) { this.providers.push(provider) console.info({ name: providerConfig.name, type: providerConfig.type }, '[References] Provider registered') } } } private createProvider(config: ProviderConfig): ReferenceProvider | null { switch (config.type) { case 'wassup': return new WassupProvider(config) case 'http-json': // TODO: implement console.warn({ name: config.name }, '[References] http-json provider not yet implemented') return null case 'http-properties': console.warn({ name: config.name }, '[References] http-properties provider not yet implemented') return null case 'static': console.warn({ name: config.name }, '[References] static provider not yet implemented') return null default: console.error({ name: config.name, type: config.type }, '[References] Unknown provider type') return null } } /** * Resolve all providers for a device in parallel. * Only triggers providers whose trigger events match the inform events. */ async resolveAll(device: DeviceRecord, context: InformContext): Promise { const applicable = this.getApplicableProviders(context.events) if (applicable.length === 0) return [] const results = await Promise.allSettled( applicable.map((p) => p.resolve(device, context)) ) const resolved: ResolvedReference[] = [] for (const result of results) { if (result.status === 'fulfilled' && result.value) { resolved.push(result.value) } else if (result.status === 'rejected') { console.error({ error: result.reason }, '[References] Provider resolution failed') } } return resolved } /** * Resolve a single provider by name. */ async resolveOne(providerName: string, device: DeviceRecord, context: InformContext): Promise { const provider = this.providers.find((p) => p.name === providerName) if (!provider) { console.warn({ providerName }, '[References] Provider not found') return null } return provider.resolve(device, context) } /** * Returns providers whose trigger events intersect with the inform events. */ private getApplicableProviders(informEvents: string[]): ReferenceProvider[] { return this.providers.filter((provider) => { const config = this.configs.find((c) => c.name === provider.name) if (!config) return false // Check if any trigger event matches the inform events for (const trigger of config.trigger) { const mappedEvents = TRIGGER_EVENT_MAP[trigger] || [trigger] for (const mapped of mappedEvents) { if (informEvents.some((e) => String(e).toUpperCase().includes(mapped.toUpperCase()))) { return true } } } return false }) } /** * Extracts the resolve value from a device based on the resolveBy config. */ static extractResolveValue(device: DeviceRecord, resolveBy: string, context: InformContext): string | null { switch (resolveBy) { case 'ip': return context.remoteIp || null case 'serialNumber': return device.deviceId?.serialNumber || null case 'oui': return device.deviceId?.oui || null case 'mac': { const allParams = [...(device.properties || []), ...(device.parameters || [])] const macParam = allParams.find((p) => p.name?.includes('MACAddress')) return macParam?.value as string || null } default: if (resolveBy.startsWith('parameter:')) { const path = resolveBy.slice('parameter:'.length) const allParams = [...(device.properties || []), ...(device.parameters || [])] const param = allParams.find((p) => p.name === path) return param?.value as string || null } return null } } getProviderNames(): string[] { return this.providers.map((p) => p.name) } getProviderConfigs(): ProviderConfig[] { return this.configs } } // Singleton let instance: ReferenceEngine | null = null export function getReferenceEngine(): ReferenceEngine { if (!instance) { instance = new ReferenceEngine() } return instance } export function resetReferenceEngine(): void { instance = null }