import { v } from 'convex/values'; import type { Doc, Id } from './_generated/dataModel'; import { internalMutation, query } from './_generated/server'; import { getOwnedSpoon, getRequiredUserId } from './model'; const syncKind = v.union( v.literal('scheduled_check'), v.literal('manual_check'), v.literal('upstream_update'), v.literal('merge_attempt'), v.literal('ai_review'), ); const syncStatus = v.union( v.literal('queued'), v.literal('running'), v.literal('clean'), v.literal('conflict'), v.literal('needs_review'), v.literal('failed'), v.literal('merged'), ); export const listRecent = query({ args: { limit: v.optional(v.number()) }, handler: async (ctx, { limit }) => { const ownerId = await getRequiredUserId(ctx); const runs = await ctx.db .query('syncRuns') .withIndex('by_owner', (q) => q.eq('ownerId', ownerId)) .order('desc') .take(limit ?? 25); return runs; }, }); export const listForSpoon = query({ args: { spoonId: v.id('spoons'), limit: v.optional(v.number()) }, handler: async (ctx, { spoonId, limit }) => { const ownerId = await getRequiredUserId(ctx); await getOwnedSpoon(ctx, spoonId, ownerId); return await ctx.db .query('syncRuns') .withIndex('by_spoon', (q) => q.eq('spoonId', spoonId)) .order('desc') .take(limit ?? 25); }, }); export const createInternal = internalMutation({ args: { spoonId: v.id('spoons'), ownerId: v.id('users'), kind: syncKind, status: syncStatus, upstreamFrom: v.optional(v.string()), upstreamTo: v.optional(v.string()), summary: v.optional(v.string()), aiAssessment: v.optional(v.string()), mergeRequestUrl: v.optional(v.string()), error: v.optional(v.string()), }, handler: async (ctx, args): Promise> => { const now = Date.now(); return await ctx.db.insert('syncRuns', { ...args, createdAt: now, updatedAt: now, }); }, }); export const patchInternal = internalMutation({ args: { syncRunId: v.id('syncRuns'), status: v.optional(syncStatus), upstreamFrom: v.optional(v.string()), upstreamTo: v.optional(v.string()), summary: v.optional(v.string()), aiAssessment: v.optional(v.string()), mergeRequestUrl: v.optional(v.string()), error: v.optional(v.string()), }, handler: async (ctx, args) => { const patch: Partial> = { updatedAt: Date.now() }; if (args.status !== undefined) patch.status = args.status; if (args.upstreamFrom !== undefined) patch.upstreamFrom = args.upstreamFrom; if (args.upstreamTo !== undefined) patch.upstreamTo = args.upstreamTo; if (args.summary !== undefined) patch.summary = args.summary; if (args.aiAssessment !== undefined) { patch.aiAssessment = args.aiAssessment; } if (args.mergeRequestUrl !== undefined) { patch.mergeRequestUrl = args.mergeRequestUrl; } if (args.error !== undefined) patch.error = args.error; await ctx.db.patch(args.syncRunId, patch); return { success: true }; }, });