All files / src/lib/db completion-telemetry.ts

44.44% Statements 4/9
100% Branches 0/0
33.33% Functions 1/3
50% Lines 4/8

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74                                                                                                  9x 9x   9x                     9x                    
/**
 * Deferred telemetry queue for shell completions.
 *
 * Shell completions write timing data here with zero Sentry SDK overhead.
 * The next normal CLI run reads, emits as Sentry metrics, and deletes.
 */
 
import { getDatabase } from "./index.js";
 
/** A queued completion telemetry entry. */
export type CompletionTelemetryEntry = {
  id: number;
  commandPath: string;
  durationMs: number;
  resultCount: number;
};
 
/**
 * Queue a completion timing entry.
 *
 * Called during the `__complete` fast-path after completions are written
 * to stdout. Uses raw SQLite (no Sentry SDK) for ~1ms overhead.
 *
 * @param entry - Completion timing data
 */
export function queueCompletionTelemetry(entry: {
  commandPath: string;
  durationMs: number;
  resultCount: number;
}): void {
  try {
    const db = getDatabase();
    db.query(
      "INSERT INTO completion_telemetry_queue (command_path, duration_ms, result_count) VALUES (?, ?, ?)"
    ).run(entry.commandPath, Math.round(entry.durationMs), entry.resultCount);
  } catch {
    // Best-effort — never fail completion for telemetry
  }
}
 
/**
 * Drain all queued completion telemetry entries.
 *
 * Atomically reads and deletes all entries using `DELETE ... RETURNING`.
 * Called during normal CLI runs inside `withTelemetry()`.
 *
 * @returns The queued entries for emission as Sentry metrics
 */
export function drainCompletionTelemetry(): CompletionTelemetryEntry[] {
  try {
    const db = getDatabase();
    // Atomic read + delete — no race with concurrent __complete processes
    const rows = db
      .query(
        "DELETE FROM completion_telemetry_queue RETURNING id, command_path, duration_ms, result_count"
      )
      .all() as {
      id: number;
      command_path: string;
      duration_ms: number;
      result_count: number;
    }[];
 
    return rows.map((row) => ({
      id: row.id,
      commandPath: row.command_path,
      durationMs: row.duration_ms,
      resultCount: row.result_count,
    }));
  } catch {
    return [];
  }
}