x402-toolpick / tree / src/counter-do.js ยท raw (this view has old GitHub links rewritten for the mirror; raw is byte-exact)
// SQLite-backed Durable Object holding the usage counters (free plan: SQLite DOs, no KV write quota involved).
// One instance ("global") per Worker; Stats.flush() sends batched {day: {event: n}} maps; upserts are atomic.
import { DurableObject } from 'cloudflare:workers';

export class StatsCounter extends DurableObject {
  constructor(ctx, env) {
    super(ctx, env);
    this.sql = ctx.storage.sql;
    this.sql.exec('CREATE TABLE IF NOT EXISTS c (day TEXT NOT NULL, ev TEXT NOT NULL, n INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (day, ev))');
    // 0.1.2: anonymized need= aggregates (see needlog.js). q is already normalized (no PII); no IP/wallet columns.
    this.sql.exec('CREATE TABLE IF NOT EXISTS needs (day TEXT NOT NULL, q TEXT NOT NULL, n INTEGER NOT NULL DEFAULT 0, no_match INTEGER NOT NULL DEFAULT 0, low_match INTEGER NOT NULL DEFAULT 0, no_ours INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (day, q))');
  }
  addNeeds(pending) {
    for (const [d, qs] of Object.entries(pending || {})) {
      if (!/^\d{4}-\d{2}-\d{2}$/.test(d)) continue;
      for (const [q, c] of Object.entries(qs || {})) {
        if (typeof q !== 'string' || q.length < 2 || q.length > 120) continue;
        const v = (k) => Math.max(0, Math.floor(Number(c?.[k]) || 0));
        if (!v('n')) continue;
        this.sql.exec('INSERT INTO needs (day, q, n, no_match, low_match, no_ours) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (day, q) DO UPDATE SET n = n + excluded.n, no_match = no_match + excluded.no_match, low_match = low_match + excluded.low_match, no_ours = no_ours + excluded.no_ours', d, q, v('n'), v('no_match'), v('low_match'), v('no_ours'));
      }
    }
    this.sql.exec("DELETE FROM needs WHERE day < date('now', '-90 days')"); // retention: 90 days
    return true;
  }
  topNeeds(since, limit = 500) {
    return this.sql.exec('SELECT q, SUM(n) AS n, SUM(no_match) AS no_match, SUM(low_match) AS low_match, SUM(no_ours) AS no_ours FROM needs WHERE day >= ? GROUP BY q ORDER BY (SUM(no_match) + SUM(low_match)) DESC, SUM(n) DESC LIMIT ?', String(since || ''), Math.min(1000, Math.max(1, Number(limit) || 500))).toArray();
  }
  add(pending) {
    for (const [d, counts] of Object.entries(pending || {})) {
      if (!/^\d{4}-\d{2}-\d{2}$/.test(d)) continue;
      for (const [ev, n] of Object.entries(counts || {})) {
        const k = Number(n);
        if (!/^[a-z0-9_:\-]{1,64}$/i.test(ev) || !Number.isFinite(k) || k <= 0) continue;
        this.sql.exec('INSERT INTO c (day, ev, n) VALUES (?, ?, ?) ON CONFLICT (day, ev) DO UPDATE SET n = n + excluded.n', d, ev, Math.floor(k));
      }
    }
    return true;
  }
  rows(since) {
    return this.sql.exec('SELECT day, ev, n FROM c WHERE day >= ? ORDER BY day DESC', String(since || '')).toArray();
  }
}

agentgate-source: code mirror on Cloudflare Pages. All repos