import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { TelemetryClient, type TaskCompletionEvent, type PredictionQuery, type PredictionResponse, type EventBuilder, } from "@mosaicstack/telemetry-client"; import { loadMosaicTelemetryConfig, toSdkConfig, type MosaicTelemetryModuleConfig, } from "./mosaic-telemetry.config"; /** * NestJS service wrapping the @mosaicstack/telemetry-client SDK. * * Provides convenience methods for tracking task completions and reading * crowd-sourced predictions. When telemetry is disabled via * MOSAIC_TELEMETRY_ENABLED=false, all methods are safe no-ops. * * This service is provided globally by MosaicTelemetryModule — any service * can inject it without importing the module explicitly. * * @example * ```typescript * @Injectable() * export class TasksService { * constructor(private readonly telemetry: MosaicTelemetryService) {} * * async completeTask(taskId: string): Promise { * // ... complete the task ... * const event = this.telemetry.eventBuilder.build({ ... }); * this.telemetry.trackTaskCompletion(event); * } * } * ``` */ @Injectable() export class MosaicTelemetryService implements OnModuleInit, OnModuleDestroy { private readonly logger = new Logger(MosaicTelemetryService.name); private client: TelemetryClient | null = null; private config: MosaicTelemetryModuleConfig | null = null; constructor(private readonly configService: ConfigService) {} /** * Initialize the telemetry client on module startup. * Reads configuration from environment variables and starts background submission. */ onModuleInit(): void { this.config = loadMosaicTelemetryConfig(this.configService); if (!this.config.enabled) { this.logger.log("Mosaic Telemetry is disabled"); return; } if (!this.config.serverUrl || !this.config.apiKey || !this.config.instanceId) { this.logger.warn( "Mosaic Telemetry is enabled but missing configuration " + "(MOSAIC_TELEMETRY_SERVER_URL, MOSAIC_TELEMETRY_API_KEY, or MOSAIC_TELEMETRY_INSTANCE_ID). " + "Telemetry will remain disabled." ); this.config = { ...this.config, enabled: false }; return; } const sdkConfig = toSdkConfig(this.config, (error: Error) => { this.logger.error(`Telemetry client error: ${error.message}`, error.stack); }); this.client = new TelemetryClient(sdkConfig); this.client.start(); const mode = this.config.dryRun ? "dry-run" : "live"; this.logger.log(`Mosaic Telemetry client started (${mode}) -> ${this.config.serverUrl}`); } /** * Stop the telemetry client on module shutdown. * Flushes any remaining queued events before stopping. */ async onModuleDestroy(): Promise { if (this.client) { this.logger.log("Stopping Mosaic Telemetry client..."); await this.client.stop(); this.client = null; this.logger.log("Mosaic Telemetry client stopped"); } } /** * Queue a task completion event for batch submission. * No-op when telemetry is disabled. * * @param event - The task completion event to track */ trackTaskCompletion(event: TaskCompletionEvent): void { if (!this.client) { return; } this.client.track(event); } /** * Get a cached prediction for the given query. * Returns null when telemetry is disabled or if not cached/expired. * * @param query - The prediction query parameters * @returns Cached prediction response, or null */ getPrediction(query: PredictionQuery): PredictionResponse | null { if (!this.client) { return null; } return this.client.getPrediction(query); } /** * Force-refresh predictions from the telemetry server. * No-op when telemetry is disabled. * * @param queries - Array of prediction queries to refresh */ async refreshPredictions(queries: PredictionQuery[]): Promise { if (!this.client) { return; } await this.client.refreshPredictions(queries); } /** * Get the EventBuilder for constructing TaskCompletionEvent objects. * Returns null when telemetry is disabled. * * @returns EventBuilder instance, or null if disabled */ get eventBuilder(): EventBuilder | null { if (!this.client) { return null; } return this.client.eventBuilder; } /** * Whether the telemetry client is currently active and running. */ get isEnabled(): boolean { return this.client?.isRunning ?? false; } /** * Number of events currently queued for submission. * Returns 0 when telemetry is disabled. */ get queueSize(): number { if (!this.client) { return 0; } return this.client.queueSize; } }