import {Collection} from './Collection' import {InjectionAware} from '../../di/InjectionAware' export type MaybeIterationItem = { done: boolean, value?: T } export type ChunkCallback = (items: Collection) => any export class StopIteration extends Error {} /** * Abstract class representing an iterable, lazy-loaded dataset. * @abstract */ export abstract class Iterable extends InjectionAware { /** * The current index of the iterable. * @type number */ protected index = 0 /** * Get the item of this iterable at the given index, if one exists. * @param {number} i * @return Promise */ abstract at(i: number): Promise /** * Get the collection of items in the given range of this iterable. * @param {number} start * @param {number} end * @return Promise */ abstract range(start: number, end: number): Promise> /** * Count the number of items in this collection. * @return Promise */ abstract count(): Promise /** * Get a copy of this iterable. * @return Iterable */ abstract clone(): Iterable /** * Return a collection of all items in this iterable. * @return Promise */ public async all(): Promise> { return this.range(0, (await this.count()) + 1) } /** * Advance to the next value of this iterable. * @return Promise */ public async next(): Promise> { const i = this.index if ( i >= await this.count() ) { return { done: true } } this.index = i + 1 return { done: false, value: await this.at(i) } } /** * Chunk the iterable into the given size and call the callback passing the chunk along. * @param {number} size * @param {ChunkCallback} callback * @return Promise */ public async chunk(size: number, callback: ChunkCallback): Promise { const total = await this.count() while ( this.index < total ) { const items = await this.range(this.index, this.index + size - 1) try { await callback(items) } catch ( error ) { if ( error instanceof StopIteration ) { break } else { throw error } } this.index += size } } /** * Advance the iterable to the given index. * @param {number} index * @return Promise */ public async seek(index: number): Promise { if ( index < 0 ) { throw new TypeError('Cannot seek to negative index.') } else if ( index >= await this.count() ) { throw new TypeError('Cannot seek past last item.') } this.index = index } /** * Peek at the next value of the iterable, without advancing. * @return Promise */ public async peek(): Promise { if ( this.index + 1 >= await this.count() ) { return undefined } else { return this.at(this.index + 1) } } /** * Reset the iterable to the first index. * @return Promise */ public async reset(): Promise { this.index = 0 } }