import { authTables } from '@convex-dev/auth/server'; import { defineSchema, defineTable } from 'convex/server'; import { v } from 'convex/values'; const applicationTables = { /* * Below is the users table definition from authTables * You can add additional fields here. You can also remove * the users table here & create a 'profiles' table if you * prefer to keep auth data separate from application data. */ users: defineTable({ name: v.optional(v.string()), image: v.optional(v.string()), email: v.optional(v.string()), emailVerificationTime: v.optional(v.number()), phone: v.optional(v.string()), phoneVerificationTime: v.optional(v.number()), isAnonymous: v.optional(v.boolean()), /* Fields below here are custom & not defined in authTables */ isAdmin: v.optional(v.boolean()), role: v.optional(v.union(v.literal('owner'), v.literal('member'))), lastSeenAt: v.optional(v.number()), themePreference: v.optional( v.union(v.literal('light'), v.literal('dark'), v.literal('system')), ), }) .index('email', ['email']) .index('phone', ['phone']) /* Indexes below here are custom & not defined in authTables */ .index('name', ['name']), gitConnections: defineTable({ userId: v.id('users'), provider: v.union( v.literal('github'), v.literal('gitea'), v.literal('gitlab'), v.literal('other'), ), providerAccountId: v.optional(v.string()), displayName: v.string(), username: v.optional(v.string()), avatarUrl: v.optional(v.string()), installationId: v.optional(v.string()), scopes: v.optional(v.array(v.string())), status: v.union( v.literal('active'), v.literal('needs_reauth'), v.literal('revoked'), ), connectedAt: v.number(), updatedAt: v.number(), }) .index('by_user', ['userId']) .index('by_user_provider', ['userId', 'provider']) .index('by_status', ['status']), spoons: defineTable({ ownerId: v.id('users'), name: v.string(), description: v.optional(v.string()), provider: v.union( v.literal('github'), v.literal('gitea'), v.literal('gitlab'), v.literal('other'), ), upstreamOwner: v.string(), upstreamRepo: v.string(), upstreamDefaultBranch: v.string(), upstreamUrl: v.string(), forkOwner: v.optional(v.string()), forkRepo: v.optional(v.string()), forkDefaultBranch: v.optional(v.string()), forkUrl: v.optional(v.string()), visibility: v.union( v.literal('public'), v.literal('private'), v.literal('internal'), v.literal('unknown'), ), maintenanceMode: v.union( v.literal('watch'), v.literal('auto_pr'), v.literal('paused'), ), syncCadence: v.union( v.literal('daily'), v.literal('weekly'), v.literal('manual'), ), productionRefStrategy: v.union( v.literal('default_branch'), v.literal('latest_release'), v.literal('tag_pattern'), ), tagPattern: v.optional(v.string()), status: v.union( v.literal('draft'), v.literal('active'), v.literal('needs_connection'), v.literal('paused'), v.literal('archived'), ), lastCheckedAt: v.optional(v.number()), lastUpstreamCommit: v.optional(v.string()), lastForkCommit: v.optional(v.string()), connectionId: v.optional(v.id('gitConnections')), githubInstallationId: v.optional(v.string()), githubRepositoryId: v.optional(v.number()), upstreamRepositoryId: v.optional(v.number()), syncStatus: v.optional( v.union( v.literal('unknown'), v.literal('up_to_date'), v.literal('behind'), v.literal('ahead'), v.literal('diverged'), v.literal('checking'), v.literal('conflict'), v.literal('error'), ), ), upstreamAheadBy: v.optional(v.number()), forkAheadBy: v.optional(v.number()), lastMergeBaseCommit: v.optional(v.string()), lastSyncRunId: v.optional(v.id('syncRuns')), lastAiReviewId: v.optional(v.id('aiReviews')), lastGithubRefreshAt: v.optional(v.number()), lastSuccessfulRefreshAt: v.optional(v.number()), lastError: v.optional(v.string()), createdAt: v.number(), updatedAt: v.number(), }) .index('by_owner', ['ownerId']) .index('by_owner_status', ['ownerId', 'status']) .index('by_owner_provider', ['ownerId', 'provider']) .index('by_upstream', ['provider', 'upstreamOwner', 'upstreamRepo']), spoonRepositoryStates: defineTable({ spoonId: v.id('spoons'), ownerId: v.id('users'), upstreamFullName: v.string(), forkFullName: v.string(), upstreamDefaultBranch: v.string(), forkDefaultBranch: v.string(), upstreamHeadSha: v.optional(v.string()), forkHeadSha: v.optional(v.string()), mergeBaseSha: v.optional(v.string()), upstreamAheadBy: v.number(), forkAheadBy: v.number(), status: v.union( v.literal('up_to_date'), v.literal('behind'), v.literal('ahead'), v.literal('diverged'), v.literal('unknown'), ), openForkPullRequestCount: v.number(), openUpstreamPullRequestCount: v.number(), lastCommitAt: v.optional(v.number()), rawCompareUrl: v.optional(v.string()), refreshedAt: v.number(), createdAt: v.number(), updatedAt: v.number(), }) .index('by_spoon', ['spoonId']) .index('by_owner', ['ownerId']) .index('by_status', ['ownerId', 'status']), spoonCommits: defineTable({ spoonId: v.id('spoons'), ownerId: v.id('users'), sha: v.string(), side: v.union(v.literal('upstream'), v.literal('fork')), message: v.string(), authorName: v.optional(v.string()), authorEmail: v.optional(v.string()), authorLogin: v.optional(v.string()), committedAt: v.optional(v.number()), htmlUrl: v.optional(v.string()), filesChanged: v.optional(v.number()), additions: v.optional(v.number()), deletions: v.optional(v.number()), createdAt: v.number(), updatedAt: v.number(), }) .index('by_spoon_side', ['spoonId', 'side']) .index('by_owner', ['ownerId']) .index('by_sha', ['spoonId', 'sha']) .index('by_committed', ['spoonId', 'committedAt']), spoonPullRequests: defineTable({ spoonId: v.id('spoons'), ownerId: v.id('users'), githubId: v.number(), number: v.number(), repoFullName: v.string(), scope: v.union( v.literal('fork'), v.literal('upstream'), v.literal('from_fork_to_upstream'), ), title: v.string(), state: v.union(v.literal('open'), v.literal('closed'), v.literal('merged')), draft: v.boolean(), authorLogin: v.optional(v.string()), baseRef: v.string(), headRef: v.string(), headRepoFullName: v.optional(v.string()), htmlUrl: v.string(), createdAtGithub: v.optional(v.number()), updatedAtGithub: v.optional(v.number()), mergedAtGithub: v.optional(v.number()), createdAt: v.number(), updatedAt: v.number(), }) .index('by_spoon', ['spoonId']) .index('by_spoon_scope', ['spoonId', 'scope']) .index('by_owner', ['ownerId']) .index('by_github_id', ['githubId']) .index('by_state', ['spoonId', 'state']), syncRuns: defineTable({ spoonId: v.id('spoons'), ownerId: v.id('users'), kind: v.union( v.literal('scheduled_check'), v.literal('manual_check'), v.literal('upstream_update'), v.literal('merge_attempt'), v.literal('ai_review'), ), status: v.union( v.literal('queued'), v.literal('running'), v.literal('clean'), v.literal('conflict'), v.literal('needs_review'), v.literal('failed'), v.literal('merged'), ), 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()), createdAt: v.number(), updatedAt: v.number(), }) .index('by_owner', ['ownerId']) .index('by_spoon', ['spoonId']) .index('by_owner_status', ['ownerId', 'status']) .index('by_created', ['createdAt']), aiReviews: defineTable({ spoonId: v.id('spoons'), ownerId: v.id('users'), syncRunId: v.optional(v.id('syncRuns')), model: v.string(), status: v.union( v.literal('queued'), v.literal('running'), v.literal('completed'), v.literal('failed'), ), reviewType: v.union( v.literal('upstream_update'), v.literal('manual_prompt'), v.literal('merge_safety'), ), inputSummary: v.string(), outputSummary: v.optional(v.string()), risk: v.union( v.literal('unknown'), v.literal('low'), v.literal('medium'), v.literal('high'), ), compatible: v.boolean(), requiresHumanReview: v.boolean(), recommendedAction: v.union( v.literal('sync'), v.literal('open_review_pr'), v.literal('manual_review'), v.literal('do_not_sync'), v.literal('unknown'), ), potentialConflicts: v.optional(v.array(v.string())), importantFiles: v.optional(v.array(v.string())), reasoningSummary: v.optional(v.string()), error: v.optional(v.string()), createdAt: v.number(), updatedAt: v.number(), completedAt: v.optional(v.number()), }) .index('by_spoon', ['spoonId']) .index('by_owner', ['ownerId']) .index('by_status', ['ownerId', 'status']) .index('by_sync_run', ['syncRunId']) .index('by_created', ['createdAt']), spoonSettings: defineTable({ spoonId: v.id('spoons'), ownerId: v.id('users'), autoRefreshEnabled: v.boolean(), autoReviewEnabled: v.boolean(), autoSyncEnabled: v.boolean(), requireAiLowRiskForSync: v.boolean(), requireCleanCompareForSync: v.boolean(), ignoredFilePatterns: v.optional(v.array(v.string())), importantFilePatterns: v.optional(v.array(v.string())), createdAt: v.number(), updatedAt: v.number(), }) .index('by_spoon', ['spoonId']) .index('by_owner', ['ownerId']), spoonRemotes: defineTable({ spoonId: v.id('spoons'), ownerId: v.id('users'), label: v.string(), url: v.string(), remoteName: v.optional(v.string()), createdAt: v.number(), updatedAt: v.number(), }) .index('by_spoon', ['spoonId']) .index('by_owner', ['ownerId']), userAiSettings: defineTable({ userId: v.id('users'), provider: v.literal('openai'), encryptedApiKey: v.optional(v.string()), apiKeyPreview: v.optional(v.string()), model: v.string(), reasoningEffort: v.union( v.literal('none'), v.literal('minimal'), v.literal('low'), v.literal('medium'), v.literal('high'), v.literal('xhigh'), ), createdAt: v.number(), updatedAt: v.number(), }) .index('by_user', ['userId']) .index('by_user_provider', ['userId', 'provider']), agentRequests: defineTable({ spoonId: v.id('spoons'), ownerId: v.id('users'), agentJobId: v.optional(v.id('agentJobs')), prompt: v.string(), requestType: v.optional( v.union( v.literal('manual_prompt'), v.literal('upstream_review'), v.literal('future_code_change'), ), ), priority: v.optional( v.union(v.literal('low'), v.literal('normal'), v.literal('high')), ), source: v.optional(v.union(v.literal('user'), v.literal('system'))), status: v.union( v.literal('draft'), v.literal('queued'), v.literal('running'), v.literal('changes_ready'), v.literal('merge_request_opened'), v.literal('failed'), v.literal('cancelled'), ), targetBranch: v.optional(v.string()), selectedSecretIds: v.optional(v.array(v.id('spoonSecrets'))), baseBranch: v.optional(v.string()), requestedBranchName: v.optional(v.string()), mergeRequestUrl: v.optional(v.string()), summary: v.optional(v.string()), error: v.optional(v.string()), createdAt: v.number(), updatedAt: v.number(), }) .index('by_owner', ['ownerId']) .index('by_spoon', ['spoonId']) .index('by_owner_status', ['ownerId', 'status']) .index('by_created', ['createdAt']), spoonSecrets: defineTable({ spoonId: v.id('spoons'), ownerId: v.id('users'), name: v.string(), encryptedValue: v.string(), valuePreview: v.optional(v.string()), description: v.optional(v.string()), createdAt: v.number(), updatedAt: v.number(), }) .index('by_spoon', ['spoonId']) .index('by_owner', ['ownerId']) .index('by_name', ['spoonId', 'name']), spoonAgentSettings: defineTable({ spoonId: v.id('spoons'), ownerId: v.id('users'), enabled: v.boolean(), defaultBaseBranch: v.optional(v.string()), branchPrefix: v.string(), installCommand: v.optional(v.string()), checkCommand: v.optional(v.string()), testCommand: v.optional(v.string()), agentModel: v.string(), reasoningEffort: v.union( v.literal('none'), v.literal('minimal'), v.literal('low'), v.literal('medium'), v.literal('high'), v.literal('xhigh'), ), maxJobDurationMs: v.number(), maxOutputBytes: v.number(), createdAt: v.number(), updatedAt: v.number(), }) .index('by_spoon', ['spoonId']) .index('by_owner', ['ownerId']), agentJobs: defineTable({ spoonId: v.id('spoons'), ownerId: v.id('users'), agentRequestId: v.id('agentRequests'), status: v.union( v.literal('queued'), v.literal('claimed'), v.literal('preparing'), v.literal('running'), v.literal('checks_running'), v.literal('changes_ready'), v.literal('draft_pr_opened'), v.literal('failed'), v.literal('cancelled'), v.literal('timed_out'), ), prompt: v.string(), baseBranch: v.string(), workBranch: v.string(), githubInstallationId: v.optional(v.string()), forkOwner: v.string(), forkRepo: v.string(), forkUrl: v.string(), upstreamOwner: v.string(), upstreamRepo: v.string(), selectedSecretIds: v.array(v.id('spoonSecrets')), model: v.string(), reasoningEffort: v.union( v.literal('none'), v.literal('minimal'), v.literal('low'), v.literal('medium'), v.literal('high'), v.literal('xhigh'), ), commitSha: v.optional(v.string()), pullRequestUrl: v.optional(v.string()), pullRequestNumber: v.optional(v.number()), summary: v.optional(v.string()), error: v.optional(v.string()), claimedBy: v.optional(v.string()), claimedAt: v.optional(v.number()), startedAt: v.optional(v.number()), completedAt: v.optional(v.number()), createdAt: v.number(), updatedAt: v.number(), }) .index('by_owner', ['ownerId']) .index('by_spoon', ['spoonId']) .index('by_request', ['agentRequestId']) .index('by_status', ['status']) .index('by_claim', ['status', 'createdAt']), agentJobEvents: defineTable({ jobId: v.id('agentJobs'), spoonId: v.id('spoons'), ownerId: v.id('users'), level: v.union( v.literal('debug'), v.literal('info'), v.literal('warn'), v.literal('error'), ), phase: v.union( v.literal('queued'), v.literal('clone'), v.literal('plan'), v.literal('edit'), v.literal('install'), v.literal('check'), v.literal('test'), v.literal('commit'), v.literal('push'), v.literal('pr'), v.literal('cleanup'), ), message: v.string(), metadata: v.optional(v.string()), createdAt: v.number(), }) .index('by_job', ['jobId']) .index('by_spoon', ['spoonId']) .index('by_owner', ['ownerId']), agentJobArtifacts: defineTable({ jobId: v.id('agentJobs'), spoonId: v.id('spoons'), ownerId: v.id('users'), kind: v.union( v.literal('plan'), v.literal('diff'), v.literal('test_output'), v.literal('summary'), v.literal('error'), v.literal('pr_body'), ), title: v.string(), content: v.string(), contentType: v.union( v.literal('text/markdown'), v.literal('text/plain'), v.literal('application/json'), v.literal('text/x-diff'), ), createdAt: v.number(), }) .index('by_job', ['jobId']) .index('by_spoon', ['spoonId']) .index('by_owner', ['ownerId']), }; export default defineSchema({ ...authTables, ...applicationTables, });