// Package store owns the single embedded SQLite database backing the zhanlu // proxy: token-usage records (the requests table) and the persisted login // credentials (the credentials table, single-tenant single row). It implements // stats.Recorder for usage tracking and exposes Load/Save for credentials. package store import ( "database/sql" "fmt" "strings" _ "modernc.org/sqlite" ) // Store is the single owner of the proxy's SQLite database handle. type Store struct { db *sql.DB } // Open opens (or creates) the database at path and ensures both tables exist. // SQLite is opened with WAL journaling and a busy timeout so concurrent reads // (stats queries) and writes (request records, credential saves) do not // collide. func Open(path string) (*Store, error) { db, err := sql.Open("sqlite", path+"?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)&_pragma=synchronous(NORMAL)&_pragma=foreign_keys(ON)") if err != nil { return nil, fmt.Errorf("open db %q: %w", path, err) } if err := ensureSchema(db); err != nil { db.Close() return nil, err } return &Store{db: db}, nil } // Close releases the database handle. func (s *Store) Close() error { return s.db.Close() } func ensureSchema(db *sql.DB) error { stmts := []string{ `CREATE TABLE IF NOT EXISTS requests ( id INTEGER PRIMARY KEY AUTOINCREMENT, ts INTEGER NOT NULL, model TEXT NOT NULL, stream INTEGER NOT NULL DEFAULT 0, prompt_tokens INTEGER NOT NULL DEFAULT 0, completion_tokens INTEGER NOT NULL DEFAULT 0, total_tokens INTEGER NOT NULL DEFAULT 0, reasoning_tokens INTEGER NOT NULL DEFAULT 0, cached_tokens INTEGER NOT NULL DEFAULT 0, status TEXT NOT NULL DEFAULT 'success', latency_ms INTEGER NOT NULL DEFAULT 0 )`, `CREATE TABLE IF NOT EXISTS credentials ( id INTEGER PRIMARY KEY CHECK (id = 1), access_key TEXT NOT NULL DEFAULT '', secret_key TEXT NOT NULL DEFAULT '', token TEXT NOT NULL DEFAULT '', api_key TEXT NOT NULL DEFAULT '', model_base_url TEXT NOT NULL DEFAULT '', email TEXT NOT NULL DEFAULT '', organization TEXT NOT NULL DEFAULT '', team TEXT NOT NULL DEFAULT '', base_url TEXT NOT NULL DEFAULT '', saved_at INTEGER NOT NULL DEFAULT 0 )`, `CREATE INDEX IF NOT EXISTS idx_requests_ts ON requests(ts)`, `CREATE INDEX IF NOT EXISTS idx_requests_model ON requests(model)`, // Ensure the single credentials row exists so UPSERTs and SELECTs always // have a target. `INSERT INTO credentials (id) VALUES (1) ON CONFLICT(id) DO NOTHING`, } for _, q := range stmts { if _, err := db.Exec(q); err != nil { return fmt.Errorf("schema: %w", err) } } // Add cached_tokens to databases created before this column existed. SQLite // returns "duplicate column name" when it already exists; that is expected // and ignored. if _, err := db.Exec(`ALTER TABLE requests ADD COLUMN cached_tokens INTEGER NOT NULL DEFAULT 0`); err != nil { if !strings.Contains(err.Error(), "duplicate column") { return fmt.Errorf("migrate cached_tokens: %w", err) } } return nil }