/** * Forward-compat shim for @mosaicstack/telemetry-client-js. * * @mosaicstack/telemetry-client-js is not yet published to the Gitea npm * registry (returns 404 as of 2026-04-04). This shim mirrors the minimal * interface that the real client will expose so that all telemetry wiring * can be implemented now and swapped for the real package when it lands. * * TODO: replace this shim with `import { ... } from '@mosaicstack/telemetry-client-js'` * once the package is published. */ export interface TelemetryEvent { /** Event name / category */ name: string; /** Arbitrary key-value payload */ properties?: Record; /** ISO timestamp — defaults to now if omitted */ timestamp?: string; } /** * Minimal interface mirroring what @mosaicstack/telemetry-client-js exposes. */ export interface TelemetryClient { /** Initialise the client (must be called before captureEvent / upload). */ init(options: TelemetryClientOptions): void; /** Queue a telemetry event for eventual upload. */ captureEvent(event: TelemetryEvent): void; /** * Flush all queued events to the remote endpoint. * In dry-run mode the client must print instead of POST. */ upload(): Promise; /** Flush and release resources. */ shutdown(): Promise; } export interface TelemetryClientOptions { /** Remote OTLP / telemetry endpoint URL */ endpoint?: string; /** Dry-run: print payloads instead of posting */ dryRun?: boolean; /** Extra labels attached to every event */ labels?: Record; } // ─── Shim implementation ───────────────────────────────────────────────────── /** * A no-network shim that buffers events and pretty-prints them in dry-run mode. * This is the ONLY implementation used until the real package is published. */ class TelemetryClientShim implements TelemetryClient { private options: TelemetryClientOptions = {}; private queue: TelemetryEvent[] = []; init(options: TelemetryClientOptions): void { // Merge options without clearing the queue — buffered events must survive // re-initialisation so that `telemetry upload` can flush them. this.options = options; } captureEvent(event: TelemetryEvent): void { this.queue.push({ ...event, timestamp: event.timestamp ?? new Date().toISOString(), }); } async upload(): Promise { const isDryRun = this.options.dryRun !== false; // dry-run is default if (isDryRun) { console.log('[dry-run] telemetry upload — no network call made'); for (const evt of this.queue) { console.log(JSON.stringify({ ...evt, labels: this.options.labels }, null, 2)); } this.queue = []; return; } // Real upload path — placeholder until real client replaces this shim. const endpoint = this.options.endpoint; if (!endpoint) { console.log('[dry-run] telemetry upload — no endpoint configured, no network call made'); for (const evt of this.queue) { console.log(JSON.stringify(evt, null, 2)); } this.queue = []; return; } // The real client is not yet published — throw so callers know no data // was actually sent. This prevents the CLI from marking an upload as // successful when only the shim is present. // TODO: remove once @mosaicstack/telemetry-client-js replaces this shim. throw new Error( `[shim] telemetry-client-js is not yet available — cannot POST to ${endpoint}. ` + 'Remote upload is supported only after the mosaicstack.dev endpoint is live.', ); } async shutdown(): Promise { await this.upload(); } } /** Singleton client instance. */ let _client: TelemetryClient | null = null; /** Return (or lazily create) the singleton telemetry client. */ export function getTelemetryClient(): TelemetryClient { if (!_client) { _client = new TelemetryClientShim(); } return _client; } /** * Replace the singleton — used in tests to inject a mock. */ export function setTelemetryClient(client: TelemetryClient): void { _client = client; } /** * Reset the singleton to null (useful in tests). */ export function resetTelemetryClient(): void { _client = null; }