feat: TypeScript telemetry client SDK v0.1.0

Standalone npm package (@mosaicstack/telemetry-client) for reporting
task-completion telemetry and querying predictions from the Mosaic
Stack Telemetry server.

- TelemetryClient with setInterval-based background flush
- EventQueue (bounded FIFO array)
- BatchSubmitter with native fetch, exponential backoff, Retry-After
- PredictionCache (Map + TTL)
- EventBuilder with auto-generated event_id/timestamp
- Zero runtime dependencies (Node 18+ native APIs)
- 43 tests, 86% branch coverage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-07 23:25:31 -06:00
commit 177720e523
26 changed files with 5643 additions and 0 deletions

59
src/prediction-cache.ts Normal file
View File

@@ -0,0 +1,59 @@
import { PredictionQuery, PredictionResponse } from './types/predictions.js';
interface CacheEntry {
response: PredictionResponse;
expiresAt: number;
}
/**
* In-memory cache for prediction responses with TTL-based expiry.
*/
export class PredictionCache {
private readonly cache = new Map<string, CacheEntry>();
private readonly ttlMs: number;
constructor(ttlMs: number) {
this.ttlMs = ttlMs;
}
/** Build a deterministic cache key from a prediction query. */
private buildKey(query: PredictionQuery): string {
return `${query.task_type}:${query.model}:${query.provider}:${query.complexity}`;
}
/** Get a cached prediction. Returns null if not cached or expired. */
get(query: PredictionQuery): PredictionResponse | null {
const key = this.buildKey(query);
const entry = this.cache.get(key);
if (!entry) {
return null;
}
if (Date.now() > entry.expiresAt) {
this.cache.delete(key);
return null;
}
return entry.response;
}
/** Store a prediction response with TTL. */
set(query: PredictionQuery, response: PredictionResponse): void {
const key = this.buildKey(query);
this.cache.set(key, {
response,
expiresAt: Date.now() + this.ttlMs,
});
}
/** Clear all cached predictions. */
clear(): void {
this.cache.clear();
}
/** Number of entries currently in cache (including potentially expired). */
get size(): number {
return this.cache.size;
}
}