Rebasely Beta: data may be purged

examples/MCP-tools Public

TypeScript · 43 lines · 1.9 KiB · at dae155a

// Calls per tool per day, in KV. One key per tool and UTC day, `calls:2026-10-10:convert_units`,// holding { calls, errors } both as the value and as the key's metadata, so /stats can read a// whole day with one list() and no gets.//// The counter is a read, then a write, so two calls that land at the same moment can count as// one, and KV takes about a minute to show a write everywhere. That is fine for seeing which// tools get used; it is not billing.export type Counts = { calls: number; errors: number };export type Day = { date: string; tools: Record<string, Counts> };const PREFIX = "calls:";const KEEP_SECONDS = 90 * 24 * 60 * 60;export const MAX_DAYS = 31;export function dayOf(date: Date): string {  return date.toISOString().slice(0, 10);}export async function countCall(kv: KVNamespace, tool: string, ok: boolean, now = new Date()): Promise<void> {  const key = `${PREFIX}${dayOf(now)}:${tool}`;  const prev = (await kv.get<Counts>(key, "json")) ?? { calls: 0, errors: 0 };  const next: Counts = { calls: prev.calls + 1, errors: prev.errors + (ok ? 0 : 1) };  await kv.put(key, JSON.stringify(next), { metadata: next, expirationTtl: KEEP_SECONDS });}// The last `days` UTC days, newest first, including days with no calls.export async function readUsage(kv: KVNamespace, days: number, now = new Date()): Promise<Day[]> {  const out: Day[] = [];  for (let i = 0; i < days; i++) {    const date = dayOf(new Date(now.getTime() - i * 86_400_000));    const prefix = `${PREFIX}${date}:`;    const tools: Record<string, Counts> = {};    let cursor: string | undefined;    do {      const page = await kv.list<Counts>({ prefix, cursor });      for (const k of page.keys) if (k.metadata) tools[k.name.slice(prefix.length)] = k.metadata;      cursor = page.list_complete ? undefined : page.cursor;    } while (cursor);    out.push({ date, tools });  }  return out;}