(xcodeBase: string)
| 353 | |
| 354 | /** Async version with per-conversation yields and non-blocking sqlite3 queries. */ |
| 355 | export async function parseXcodeDatabasesAsync(xcodeBase: string): Promise<Session[]> { |
| 356 | const sessions: Session[] = []; |
| 357 | const dbFiles = findXcodeDbFiles(xcodeBase); |
| 358 | |
| 359 | // Check sqlite3 is available (neutral cwd — see note on the sync variant). |
| 360 | try { execFileSync('sqlite3', ['--version'], { encoding: 'utf-8', ...SQLITE_PROBE_OPTS }); } |
| 361 | catch { return sessions; } |
| 362 | |
| 363 | for (const dbPath of dbFiles) { |
| 364 | let conversations: XcodeConversationRow[]; |
| 365 | try { |
| 366 | const raw = await sqliteQueryAsync(dbPath, 'SELECT id, title, createdAt, updatedAt FROM Conversation'); |
| 367 | if (!raw.trim()) continue; |
| 368 | conversations = JSON.parse(raw) as XcodeConversationRow[]; |
| 369 | } catch { continue; } |
| 370 | |
| 371 | for (const conv of conversations) { |
| 372 | if (!/^[\w-]+$/.test(conv.id)) continue; |
| 373 | |
| 374 | let turns: XcodeTurnRow[]; |
| 375 | try { |
| 376 | turns = await sqliteQueryTurnsAsync(dbPath, conv.id); |
| 377 | } catch { continue; } |
| 378 | |
| 379 | const session = buildXcodeSession(conv, turns); |
| 380 | if (session) sessions.push(session); |
| 381 | |
| 382 | // Yield between conversations so the event loop stays responsive |
| 383 | await new Promise<void>(r => setTimeout(r, 0)); |
| 384 | } |
| 385 | } |
| 386 | |
| 387 | return sessions; |
| 388 | } |
no test coverage detected