11import { v } from 'convex/values'
22import { internal } from './_generated/api'
33import type { Doc , Id } from './_generated/dataModel'
4- import type { ActionCtx , DatabaseReader } from './_generated/server'
4+ import type { ActionCtx , DatabaseReader , DatabaseWriter } from './_generated/server'
55import { action , internalMutation , internalQuery } from './_generated/server'
66import { publishSoulVersionForUser } from './lib/soulPublish'
77import { SOUL_SEED_DISPLAY_NAME , SOUL_SEED_HANDLE , SOUL_SEED_KEY , SOUL_SEEDS } from './seedSouls'
@@ -10,6 +10,11 @@ const SEED_LOCK_STALE_MS = 10 * 60 * 1000
1010
1111type SeedStateDoc = Doc < 'githubBackupSyncState' >
1212
13+ type SeedStartDecision = {
14+ started : boolean
15+ reason : 'done' | 'running' | 'patched' | 'inserted'
16+ }
17+
1318async function getSeedState ( ctx : { db : DatabaseReader } ) : Promise < SeedStateDoc | null > {
1419 const entries = ( await ctx . db
1520 . query ( 'githubBackupSyncState' )
@@ -19,6 +24,28 @@ async function getSeedState(ctx: { db: DatabaseReader }): Promise<SeedStateDoc |
1924 return entries [ 0 ] ?? null
2025}
2126
27+ async function cleanupSeedState ( ctx : { db : DatabaseWriter } , keepId : Id < 'githubBackupSyncState' > ) {
28+ const entries = ( await ctx . db
29+ . query ( 'githubBackupSyncState' )
30+ . withIndex ( 'by_key' , ( q ) => q . eq ( 'key' , SOUL_SEED_KEY ) )
31+ . order ( 'desc' )
32+ . take ( 50 ) ) as SeedStateDoc [ ]
33+
34+ for ( const entry of entries ) {
35+ if ( entry . _id === keepId ) continue
36+ await ctx . db . delete ( entry . _id )
37+ }
38+ }
39+
40+ export function decideSeedStart ( existing : SeedStateDoc | null , now : number ) : SeedStartDecision {
41+ const cursor = existing ?. cursor ?? null
42+ if ( cursor === 'done' ) return { started : false , reason : 'done' }
43+ if ( cursor === 'running' && existing && now - existing . updatedAt < SEED_LOCK_STALE_MS ) {
44+ return { started : false , reason : 'running' }
45+ }
46+ return existing ? { started : true , reason : 'patched' } : { started : true , reason : 'inserted' }
47+ }
48+
2249export const getSoulSeedStateInternal = internalQuery ( {
2350 args : { } ,
2451 handler : async ( ctx ) => getSeedState ( ctx ) ,
@@ -31,13 +58,16 @@ export const setSoulSeedStateInternal = internalMutation({
3158 const now = Date . now ( )
3259 if ( existing ) {
3360 await ctx . db . patch ( existing . _id , { cursor : args . status , updatedAt : now } )
61+ await cleanupSeedState ( ctx , existing . _id )
3462 return existing . _id
3563 }
36- return ctx . db . insert ( 'githubBackupSyncState' , {
64+ const id = await ctx . db . insert ( 'githubBackupSyncState' , {
3765 key : SOUL_SEED_KEY ,
3866 cursor : args . status ,
3967 updatedAt : now ,
4068 } )
69+ await cleanupSeedState ( ctx , id )
70+ return id
4171 } ,
4272} )
4373
@@ -46,23 +76,22 @@ export const tryStartSoulSeedInternal = internalMutation({
4676 handler : async ( ctx ) => {
4777 const now = Date . now ( )
4878 const existing = await getSeedState ( ctx )
49- const cursor = existing ?. cursor ?? null
79+ const decision = decideSeedStart ( existing , now )
5080
51- if ( cursor === 'done' ) return { started : false , reason : 'done' as const }
52- if ( cursor === 'running' && existing && now - existing . updatedAt < SEED_LOCK_STALE_MS ) {
53- return { started : false , reason : 'running' as const }
54- }
81+ if ( ! decision . started ) return decision
5582
5683 if ( existing ) {
5784 await ctx . db . patch ( existing . _id , { cursor : 'running' , updatedAt : now } )
85+ await cleanupSeedState ( ctx , existing . _id )
5886 return { started : true , reason : 'patched' as const }
5987 }
6088
61- await ctx . db . insert ( 'githubBackupSyncState' , {
89+ const id = await ctx . db . insert ( 'githubBackupSyncState' , {
6290 key : SOUL_SEED_KEY ,
6391 cursor : 'running' ,
6492 updatedAt : now ,
6593 } )
94+ await cleanupSeedState ( ctx , id )
6695 return { started : true , reason : 'inserted' as const }
6796 } ,
6897} )
0 commit comments