feat(#369): install @mosaicstack/telemetry-client in API
- Add .npmrc with scoped Gitea npm registry for @mosaicstack packages - Create MosaicTelemetryModule (global, lifecycle-aware) at apps/api/src/mosaic-telemetry/ - Create MosaicTelemetryService wrapping TelemetryClient with convenience methods: trackTaskCompletion, getPrediction, refreshPredictions, eventBuilder - Create mosaic-telemetry.config.ts for env var integration via NestJS ConfigService - Register MosaicTelemetryModule in AppModule - Add 32 unit tests covering module init, service methods, disabled mode, dry-run mode, and lifecycle management Refs #369 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
164
apps/api/src/mosaic-telemetry/mosaic-telemetry.service.ts
Normal file
164
apps/api/src/mosaic-telemetry/mosaic-telemetry.service.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
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<void> {
|
||||
* // ... 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<void> {
|
||||
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<void> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user