/** * Unified Database Layer for vclaw-cli * * Thin pass-through to the SQLite backend (`./db`). Job state lives in * a process-local `veo-cli.db` SQLite file; canonical project state is * the parent videoclaw CLI's `projects//` JSON layout. Cloud * state is out of scope (see Phase 9c v3 design spec). * * Functions remain `async` to preserve call-site shape; the underlying * SQLite calls are synchronous and wrapped in `Promise.resolve(...)`. */ import * as sqlite from "./db"; export type BatchStatus = sqlite.BatchStatus; export type JobStatus = sqlite.JobStatus; export type Batch = sqlite.Batch; export type Job = sqlite.Job; export type BatchStats = sqlite.BatchStats; export type UseApiHistoryStatus = sqlite.UseApiHistoryStatus; export type UseApiHistoryEntry = sqlite.UseApiHistoryEntry; export type { ImageCacheEntry } from "./db"; export type { WedgedBatch, WedgedJob } from "./db"; /** * Initialize the SQLite database (creates tables on first run). */ export async function initDB(_email?: string): Promise { sqlite.initDB(); } /** * Close the database connection. */ export function closeDB(): void { sqlite.closeDB(); } /** * MD5 hash of prompts content (used for active-batch lookup). */ export function hashPrompts(content: string): string { return sqlite.hashPrompts(content); } // ============================================================================ // BATCH OPERATIONS // ============================================================================ export async function createBatch( promptsFile: string, promptsHash: string, totalJobs: number, _options?: { backend?: "direct" | "useapi"; quality?: "free" | "fast" | "quality" | "veo2"; aspectRatio?: "16:9" | "9:16" | "1:1"; } ): Promise { return sqlite.createBatch(promptsFile, promptsHash, totalJobs); } export async function getBatch(id: string | number): Promise { return sqlite.getBatch(Number(id)); } export async function getActiveBatch( promptsFile: string, promptsHash: string ): Promise { return sqlite.getActiveBatch(promptsFile, promptsHash); } export async function getMostRecentIncompleteBatch(): Promise { return sqlite.getMostRecentIncompleteBatch(); } export async function updateBatchStatus( batchId: string | number, status: sqlite.BatchStatus, projectId?: string ): Promise { sqlite.updateBatchStatus(Number(batchId), status, projectId); } export async function updateBatchCounts(batchId: string | number): Promise { sqlite.updateBatchCounts(Number(batchId)); } // ============================================================================ // JOB OPERATIONS // ============================================================================ export async function createJobs( batchId: string | number, prompts: Array<{ index: number; text: string; type: string; tag: string | null; }> ): Promise { sqlite.createJobs(Number(batchId), prompts); } export async function getJob(id: string | number): Promise { return sqlite.getJob(Number(id)); } export async function getBatchJobs(batchId: string | number): Promise { return sqlite.getBatchJobs(Number(batchId)); } export async function getPendingJobs(batchId: string | number): Promise { return sqlite.getPendingJobs(Number(batchId)); } export async function startJob( jobId: string | number, _useapiJobId?: string ): Promise { sqlite.startJob(Number(jobId)); } export async function completeJob( jobId: string | number, videoPath: string, durationMs: number, creditsUsed: number = 10, _options?: { videoUrl?: string; mediaId?: string; altVideoPath?: string; altVideoUrl?: string; altMediaId?: string; captchaCreditsUsed?: number; } ): Promise { sqlite.completeJob(Number(jobId), videoPath, durationMs, creditsUsed); } export async function failJob( jobId: string | number, errorMessage: string ): Promise { sqlite.failJob(Number(jobId), errorMessage); } export async function getBatchStats( batchId: string | number ): Promise { return sqlite.getBatchStats(Number(batchId)); } export async function listBatches(limit: number = 20): Promise { return sqlite.listBatches(limit); } export async function getJobHistory( limit: number = 20 ): Promise> { return sqlite.getJobHistory(limit); } export async function resetFailedJobs(batchId: string | number): Promise { return sqlite.resetFailedJobs(Number(batchId)); } export async function cancelBatch(batchId: string | number): Promise { sqlite.cancelBatch(Number(batchId)); } export async function isBatchComplete(batchId: string | number): Promise { return sqlite.isBatchComplete(Number(batchId)); } export async function checkAndCompleteBatch(batchId: string | number): Promise { sqlite.checkAndCompleteBatch(Number(batchId)); } export async function getAverageJobDuration(defaultMs: number = 90_000): Promise { return sqlite.getAverageJobDuration(defaultMs); } // ============================================================================ // useapi.net History Tracking // ============================================================================ export async function recordUseApiHistory( entry: Omit ): Promise { return sqlite.recordUseApiHistory(entry); } export async function getUseApiHistory( limit: number = 100 ): Promise { return sqlite.getUseApiHistory(limit); } export async function getUseApiStats(hours: number = 24): Promise<{ success: number; failed: number; rateLimited: number; timeout: number; totalCost: number; avgDurationMs: number | null; }> { return sqlite.getUseApiStats(hours); } export async function cleanupUseApiHistory(daysToKeep: number = 30): Promise { return sqlite.cleanupUseApiHistory(daysToKeep); } // ============================================================================ // IMAGE UPLOAD CACHE (SQLite-only, always was) // ============================================================================ export function getImageCache( fileHash: string, aspectRatio: "landscape" | "portrait", backend: "direct" | "useapi" ): sqlite.ImageCacheEntry | null { return sqlite.getImageCache(fileHash, aspectRatio, backend); } export function setImageCache( entry: Omit ): number { return sqlite.setImageCache(entry); } export function getImageCacheStats(): { totalEntries: number; directEntries: number; useapiEntries: number; totalSizeBytes: number; } { return sqlite.getImageCacheStats(); } export function cleanupImageCache(keepPerBackend: number = 500): number { return sqlite.cleanupImageCache(keepPerBackend); } export function hashFileContents(content: ArrayBuffer): string { return sqlite.hashFileContents(content); } /** * Batches/jobs a killed render left stuck in `running`. See `findWedged` in * ./db for why the staleness window is the whole safety story. */ export async function findWedged( staleMinutes: number, ): Promise<{ batches: sqlite.WedgedBatch[]; jobs: sqlite.WedgedJob[] }> { return sqlite.findWedged(staleMinutes); } /** Mark orphaned running jobs failed (their batch already reached terminal). */ export async function failStuckJobs(jobIds: number[]): Promise { return sqlite.failStuckJobs(jobIds); }