AppView in a box as a Vite plugin thing hatk.dev
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: only trigger resync for genuinely new collections

Previously, migrateSchema checked ALL collection tables for emptiness on
every startup. Collections with 0 rows (e.g. blocks when nobody has
blocked anyone) would trigger a full resync of all repos on every
restart, creating an infinite loop.

Now we track which tables were just created this startup and only trigger
resync for those genuinely new collections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+16 -16
+1 -1
packages/hatk/package.json
··· 1 1 { 2 2 "name": "@hatk/hatk", 3 - "version": "0.0.1-alpha.52", 3 + "version": "0.0.1-alpha.53", 4 4 "license": "MIT", 5 5 "bin": { 6 6 "hatk": "dist/cli.js"
+15 -15
packages/hatk/src/database/db.ts
··· 247 247 248 248 export async function migrateSchema(tableSchemas: TableSchema[]): Promise<MigrationChange[]> { 249 249 const changes: MigrationChange[] = [] 250 + const newCollections = new Set<string>() 250 251 251 252 for (const schema of tableSchemas) { 252 253 if (schema.columns.length === 0) continue // generic JSON storage, skip 253 254 254 255 const tableName = schema.collection 255 256 const existingCols = await getExistingColumns(tableName) 256 - if (existingCols.size === 0) continue // table just created, nothing to migrate 257 + if (existingCols.size === 0) { 258 + newCollections.add(schema.collection) 259 + continue // table just created, nothing to migrate 260 + } 257 261 258 262 // Expected columns: base columns (uri, cid, did, indexed_at) + schema columns 259 263 const expectedCols = new Map<string, string>() ··· 320 324 await applyMigrationChanges(changes) 321 325 } 322 326 323 - // Check for empty collection tables — these are newly added and need backfill 324 - // Skip on fresh DB (no repos yet) since backfill runs naturally 325 - const [hasRepos] = await all(`SELECT 1 FROM _repos LIMIT 1`) 326 - if (hasRepos) { 327 - for (const schema of tableSchemas) { 328 - if (schema.columns.length === 0) continue 329 - try { 330 - const [row] = await all(`SELECT 1 FROM ${schema.tableName} LIMIT 1`) 331 - if (!row) { 332 - await run(`UPDATE _repos SET status = 'pending' WHERE status = 'active'`) 333 - emit('migration', 'new_collection', { collection: schema.collection }) 334 - break // only need to mark once 335 - } 336 - } catch {} 327 + // Trigger backfill only for genuinely new collections (tables created this startup) 328 + // Previously this checked ALL empty tables, which caused infinite resync loops 329 + // for collections that are legitimately empty (e.g. blocks when nobody has blocked) 330 + if (newCollections.size > 0) { 331 + const [hasRepos] = await all(`SELECT 1 FROM _repos LIMIT 1`) 332 + if (hasRepos) { 333 + await run(`UPDATE _repos SET status = 'pending' WHERE status = 'active'`) 334 + for (const collection of newCollections) { 335 + emit('migration', 'new_collection', { collection }) 336 + } 337 337 } 338 338 } 339 339