import { internalMutation } from "./_generated/server"; import { v } from "convex/values"; import { internal } from "./_generated/api"; const WEEK_MS = 7 * 24 * 60 * 60 * 1000; async function cleanupByCreationTime(ctx: any, table: string, cutoffMs: number, maxDeletes: number, dryRun: boolean) { const batchSize = 200; let deleted = 0; while (deleted < maxDeletes) { const take = Math.min(batchSize, maxDeletes - deleted); const rows = await ctx.db.query(table as any).order("asc").take(take); if (!rows.length) break; let reachedNewer = false; for (const row of rows) { const created = typeof row._creationTime === "number" ? row._creationTime : 0; if (created >= cutoffMs) { reachedNewer = true; break; } if (!dryRun) { await ctx.db.delete(row._id); } deleted += 1; if (deleted >= maxDeletes) break; } if (reachedNewer) break; } // 说明:若本次达到上限,可能还有更多旧数据;由调用方决定是否继续调度。 return deleted; } export const cleanupWeekly = internalMutation({ args: { dryRun: v.optional(v.boolean()), maxDeletes: v.optional(v.number()), }, handler: async (ctx, args) => { const dryRun = Boolean(args.dryRun); const maxDeletes = Math.max(100, Math.min(50_000, Math.floor(args.maxDeletes ?? 20_000))); const cutoffMs = Date.now() - WEEK_MS; // 说明: // - authRefreshTokens/authSessions:来自 @convex-dev/auth,按“只保留最近 7 天”的要求直接清理旧记录。 // - jobs:异步任务表,保留最近 7 天即可(避免长期堆积)。 const perTable = Math.max(100, Math.floor(maxDeletes / 3)); const deletedAuthRefreshTokens = await cleanupByCreationTime( ctx, "authRefreshTokens", cutoffMs, perTable, dryRun, ); const deletedAuthSessions = await cleanupByCreationTime(ctx, "authSessions", cutoffMs, perTable, dryRun); const deletedJobs = await cleanupByCreationTime(ctx, "jobs", cutoffMs, maxDeletes - perTable * 2, dryRun); // 若达到上限,兜底再跑一轮(避免一次删太多导致超时) const hitLimit = deletedAuthRefreshTokens >= perTable || deletedAuthSessions >= perTable || deletedJobs >= maxDeletes - perTable * 2; if (!dryRun && hitLimit) { await ctx.scheduler.runAfter(60_000, (internal as any).maintenance.cleanupWeekly, { dryRun: false, maxDeletes }); } return { ok: true, dryRun, cutoffMs, deleted: { authRefreshTokens: deletedAuthRefreshTokens, authSessions: deletedAuthSessions, jobs: deletedJobs, }, rescheduled: !dryRun && hitLimit, }; }, });