( candidates: Candidate[], limit: number | undefined, offset: number, )
| 233 | } |
| 234 | |
| 235 | async function applySortAndLimit( |
| 236 | candidates: Candidate[], |
| 237 | limit: number | undefined, |
| 238 | offset: number, |
| 239 | ): Promise<SessionInfo[]> { |
| 240 | candidates.sort(compareDesc) |
| 241 | |
| 242 | const sessions: SessionInfo[] = [] |
| 243 | // limit: 0 means "no limit" (matches getSessionMessages semantics) |
| 244 | const want = limit && limit > 0 ? limit : Infinity |
| 245 | let skipped = 0 |
| 246 | // Dedup post-filter: since candidates are sorted mtime-desc, the first |
| 247 | // non-null read per sessionId is naturally the newest valid copy. |
| 248 | // Pre-filter dedup would drop a session entirely if its newest-mtime |
| 249 | // copy is unreadable/empty, diverging from the no-stat readAllAndSort path. |
| 250 | const seen = new Set<string>() |
| 251 | |
| 252 | for (let i = 0; i < candidates.length && sessions.length < want; ) { |
| 253 | const batchEnd = Math.min(i + READ_BATCH_SIZE, candidates.length) |
| 254 | const batch = candidates.slice(i, batchEnd) |
| 255 | const results = await Promise.all(batch.map(readCandidate)) |
| 256 | for (let j = 0; j < results.length && sessions.length < want; j++) { |
| 257 | i++ |
| 258 | const r = results[j] |
| 259 | if (!r) continue |
| 260 | if (seen.has(r.sessionId)) continue |
| 261 | seen.add(r.sessionId) |
| 262 | if (skipped < offset) { |
| 263 | skipped++ |
| 264 | continue |
| 265 | } |
| 266 | sessions.push(r) |
| 267 | } |
| 268 | } |
| 269 | |
| 270 | return sessions |
| 271 | } |
| 272 | |
| 273 | /** |
| 274 | * Read-all path for when no limit/offset is set. Skips the stat pass |
no test coverage detected