// run an async fn over items with at most `limit` in flight at once, resolving
// to the results in input order. a bounded-concurrency map: `limit` workers
// pull from a shared cursor until the list is drained. the outbox flush uses it
// so a deep queue doesn't fire every scope's request at once on reconnect (a
// thundering herd). a rejecting fn propagates like Promise.all — the first
// rejection wins and already-started work still settles
export const mapLimit = async <T, R>(
  items: readonly T[],
  limit: number,
  fn: (item: T, index: number) => Promise<R>,
): Promise<R[]> => {
  const results = new Array<R>(items.length)
  let cursor = 0
  const worker = async (): Promise<void> => {
    while (cursor < items.length) {
      const index = cursor++
      results[index] = await fn(items[index]!, index)
    }
  }
  const workers = Array.from(
    {length: Math.max(1, Math.min(limit, items.length))},
    worker,
  )
  await Promise.all(workers)
  return results
}
