chunk<T>() and chunkAsync<T>() batch iterables

TypeScript
/** Lazily split any iterable into arrays of at most `size` items. */
export function* chunk<T>(items: Iterable<T>, size: number): Generator<T[], void, void> {
  if (!Number.isInteger(size) || size < 1) throw new RangeError(`size must be a positive integer, got ${size}`);
  let batch: T[] = [];
  for (const item of items) {
    batch.push(item);
    if (batch.length === size) {
      yield batch;
      batch = [];
    }
  }
  if (batch.length > 0) yield batch;
}

/** Batch an async source such as a paginated API stream. */
export async function* chunkAsync<T>(items: AsyncIterable<T>, size: number): AsyncGenerator<T[], void, void> {
  if (!Number.isInteger(size) || size < 1) throw new RangeError(`size must be a positive integer, got ${size}`);
  let batch: T[] = [];
  for await (const item of items) {
    batch.push(item);
    if (batch.length === size) {
      yield batch;
      batch = [];
    }
  }
  if (batch.length > 0) yield batch;
}

// for (const page of chunk(ids, 100)) await api.fetchMany(page);