evnty
Version:
Async-first, reactive event handling library for complex event flows in browser and Node.js
900 lines (812 loc) • 32.4 kB
text/typescript
import { mergeIterables, toAsyncIterable, pipe, isThenable } from './utils.js';
import { AnyIterable } from './types.js';
const enum OpKind {
MAP,
FILTER,
FILTER_MAP,
AWAITED,
INSPECT,
ENUMERATE,
TAKE,
DROP,
TAKE_WHILE,
DROP_WHILE,
REDUCE,
FLAT_MAP,
EXPAND,
}
/**
* @internal
* Represents a fusible operation that can be combined with other operations.
*/
type FusedOp =
| { kind: OpKind.MAP; fn: (value: unknown, index: number) => unknown }
| { kind: OpKind.FILTER; fn: (value: unknown, index: number) => unknown }
| { kind: OpKind.FILTER_MAP; fn: (value: unknown, index: number) => unknown }
| { kind: OpKind.AWAITED }
| { kind: OpKind.INSPECT; fn: (value: unknown, index: number) => unknown }
| { kind: OpKind.ENUMERATE; start: number }
| { kind: OpKind.TAKE; limit: number }
| { kind: OpKind.DROP; count: number }
| { kind: OpKind.TAKE_WHILE; fn: (value: unknown, index: number) => unknown }
| { kind: OpKind.DROP_WHILE; fn: (value: unknown, index: number) => unknown }
| { kind: OpKind.REDUCE; fn: (acc: unknown, value: unknown, index: number) => unknown; init: unknown; hasInit: boolean }
| { kind: OpKind.FLAT_MAP; fn: (value: unknown, index: number) => AsyncIterable<unknown, void, unknown> }
| { kind: OpKind.EXPAND; fn: (value: unknown, index: number) => Iterable<unknown> | Promise<Iterable<unknown>> };
class OpState {
initialized = false;
constructor(
public index: number,
public remaining: number,
public dropping: boolean,
public value: unknown,
) {}
static from(this: void, op: FusedOp): OpState {
switch (op.kind) {
case OpKind.ENUMERATE:
return new OpState(op.start, 0, false, undefined);
case OpKind.TAKE:
return new OpState(0, op.limit, false, undefined);
case OpKind.DROP:
return new OpState(0, op.count, false, undefined);
case OpKind.DROP_WHILE:
return new OpState(0, 0, true, undefined);
case OpKind.REDUCE:
return new OpState(0, 0, false, op.init);
default:
return new OpState(0, 0, false, undefined);
}
}
}
class ProcessResult {
constructor(
readonly value: unknown,
readonly shouldYield: boolean,
readonly done: boolean,
readonly expandIterator: Iterator<unknown> | null,
readonly expandOpIndex: number,
readonly flatMapIterator: AsyncIterator<unknown> | null,
readonly flatMapOpIndex: number,
) {}
static continue(): ProcessResult {
return new ProcessResult(undefined, false, false, null, -1, null, -1);
}
static yield(value: unknown): ProcessResult {
return new ProcessResult(value, true, false, null, -1, null, -1);
}
static done(): ProcessResult {
return new ProcessResult(undefined, false, true, null, -1, null, -1);
}
static expand(iterator: Iterator<unknown>, opIndex: number): ProcessResult {
return new ProcessResult(undefined, false, false, iterator, opIndex, null, -1);
}
static flatMap(iterator: AsyncIterator<unknown>, opIndex: number): ProcessResult {
return new ProcessResult(undefined, false, false, null, -1, iterator, opIndex);
}
}
function findTakeStates(ops: FusedOp[], opStates: OpState[]): OpState[] {
const takeStates: OpState[] = [];
for (let i = 0; i < ops.length; i++) {
if (ops[i].kind === OpKind.TAKE) takeStates.push(opStates[i]);
}
return takeStates;
}
function hasExpandingOps(ops: FusedOp[]): boolean {
for (let i = 0; i < ops.length; i++) {
const kind = ops[i].kind;
if (kind === OpKind.FLAT_MAP || kind === OpKind.EXPAND) return true;
}
return false;
}
function checkTakeExhausted(takeStates: OpState[]): boolean {
for (let i = 0; i < takeStates.length; i++) {
if (takeStates[i].remaining <= 0) return true;
}
return false;
}
async function processOps(inputValue: unknown, ops: FusedOp[], opStates: OpState[], startIndex: number): Promise<ProcessResult> {
let value = inputValue;
for (let i = startIndex; i < ops.length; i++) {
const op = ops[i];
const state = opStates[i];
switch (op.kind) {
case OpKind.MAP:
value = op.fn(value, state.index++);
break;
case OpKind.FILTER: {
const result = op.fn(value, state.index++);
const passed = isThenable(result) ? await result : result;
if (!passed) return ProcessResult.continue();
break;
}
case OpKind.FILTER_MAP: {
const result = op.fn(value, state.index++);
const resolved = isThenable(result) ? await result : result;
if (resolved === undefined) return ProcessResult.continue();
value = resolved;
break;
}
case OpKind.AWAITED:
value = await value;
break;
case OpKind.INSPECT: {
const result = op.fn(value, state.index++);
if (isThenable(result)) await result;
break;
}
case OpKind.ENUMERATE:
value = [state.index++, value];
break;
case OpKind.TAKE:
if (state.remaining <= 0) return ProcessResult.done();
state.remaining--;
break;
case OpKind.TAKE_WHILE: {
const result = op.fn(value, state.index++);
const passed = isThenable(result) ? await result : result;
if (!passed) return ProcessResult.done();
break;
}
case OpKind.DROP:
if (state.remaining > 0) {
state.remaining--;
return ProcessResult.continue();
}
break;
case OpKind.DROP_WHILE:
if (state.dropping) {
const result = op.fn(value, state.index++);
const passed = isThenable(result) ? await result : result;
if (passed) return ProcessResult.continue();
state.dropping = false;
}
break;
case OpKind.REDUCE: {
if (!state.initialized) {
state.initialized = true;
if (op.hasInit) {
const result = op.fn(state.value, value, state.index++);
state.value = isThenable(result) ? await result : result;
value = state.value;
} else {
state.value = value;
return ProcessResult.continue();
}
} else {
const result = op.fn(state.value, value, state.index++);
state.value = isThenable(result) ? await result : result;
value = state.value;
}
break;
}
case OpKind.FLAT_MAP: {
const iterable = op.fn(value, state.index++);
return ProcessResult.flatMap(iterable[Symbol.asyncIterator](), i);
}
case OpKind.EXPAND: {
const result = op.fn(value, state.index++);
const expanded = isThenable(result) ? await result : result;
return ProcessResult.expand(expanded[Symbol.iterator](), i);
}
}
}
return ProcessResult.yield(value);
}
async function processOpsSimple(inputValue: unknown, ops: FusedOp[], opStates: OpState[]): Promise<{ value: unknown; shouldYield: boolean; done: boolean }> {
let value = inputValue;
for (let i = 0; i < ops.length; i++) {
const op = ops[i];
const state = opStates[i];
switch (op.kind) {
case OpKind.MAP:
value = op.fn(value, state.index++);
break;
case OpKind.FILTER: {
const result = op.fn(value, state.index++);
const passed = isThenable(result) ? await result : result;
if (!passed) return { value: undefined, shouldYield: false, done: false };
break;
}
case OpKind.FILTER_MAP: {
const result = op.fn(value, state.index++);
const resolved = isThenable(result) ? await result : result;
if (resolved === undefined) return { value: undefined, shouldYield: false, done: false };
value = resolved;
break;
}
case OpKind.AWAITED:
value = await value;
break;
case OpKind.INSPECT: {
const result = op.fn(value, state.index++);
if (isThenable(result)) await result;
break;
}
case OpKind.ENUMERATE:
value = [state.index++, value];
break;
case OpKind.TAKE:
state.remaining--;
break;
case OpKind.TAKE_WHILE: {
const result = op.fn(value, state.index++);
const passed = isThenable(result) ? await result : result;
if (!passed) return { value: undefined, shouldYield: false, done: true };
break;
}
case OpKind.DROP:
if (state.remaining > 0) {
state.remaining--;
return { value: undefined, shouldYield: false, done: false };
}
break;
case OpKind.DROP_WHILE:
if (state.dropping) {
const result = op.fn(value, state.index++);
const passed = isThenable(result) ? await result : result;
if (passed) return { value: undefined, shouldYield: false, done: false };
state.dropping = false;
}
break;
case OpKind.REDUCE: {
if (!state.initialized) {
state.initialized = true;
if ((op as { hasInit: boolean }).hasInit) {
const result = op.fn(state.value, value, state.index++);
state.value = isThenable(result) ? await result : result;
value = state.value;
} else {
state.value = value;
return { value: undefined, shouldYield: false, done: false };
}
} else {
const result = op.fn(state.value, value, state.index++);
state.value = isThenable(result) ? await result : result;
value = state.value;
}
break;
}
}
}
return { value, shouldYield: true, done: false };
}
function collectOps(iter: AsyncIteratorObject<unknown, unknown, unknown>): FusedOp[] {
const ops: FusedOp[] = [];
let current: AsyncIteratorObject<unknown, unknown, unknown> | null = iter;
while (current) {
const op = current.op;
if (op) ops.push(op);
current = current.parent;
}
ops.reverse();
return ops;
}
function getSource(iter: AsyncIteratorObject<unknown, unknown, unknown>): AsyncIterable<unknown, unknown, unknown> {
let current = iter;
while (current.parent) {
current = current.parent;
}
return current.iterable;
}
function createSimpleIterable(source: AsyncIterable<unknown, unknown, unknown>, ops: FusedOp[]): AsyncIterable<unknown, void, unknown> {
return {
[Symbol.asyncIterator]: () => {
const iterator = source[Symbol.asyncIterator]();
const opStates = ops.map(OpState.from);
const takeStates = findTakeStates(ops, opStates);
let done = false;
return {
async next(): Promise<IteratorResult<unknown, void>> {
while (!done) {
if (checkTakeExhausted(takeStates)) {
done = true;
await iterator.return?.();
return { value: undefined, done: true };
}
const sourceResult = await iterator.next();
if (sourceResult.done) {
done = true;
return { value: undefined, done: true };
}
const result = await processOpsSimple(sourceResult.value, ops, opStates);
if (result.done) {
done = true;
await iterator.return?.();
return { value: undefined, done: true };
}
if (result.shouldYield) return { value: result.value, done: false };
}
return { value: undefined, done: true };
},
async return(returnValue?: unknown): Promise<IteratorResult<unknown, void>> {
done = true;
await iterator.return?.(returnValue);
return { value: undefined, done: true };
},
async throw(error?: unknown): Promise<IteratorResult<unknown, void>> {
done = true;
if (iterator.throw) {
await iterator.throw(error);
}
throw error;
},
};
},
};
}
type InnerFrame = { type: 'expand'; iterator: Iterator<unknown>; opIndex: number } | { type: 'flatMap'; iterator: AsyncIterator<unknown>; opIndex: number };
function createExpandingIterable(source: AsyncIterable<unknown, unknown, unknown>, ops: FusedOp[]): AsyncIterable<unknown, void, unknown> {
return {
[Symbol.asyncIterator]: () => {
const iterator = source[Symbol.asyncIterator]();
const opStates = ops.map(OpState.from);
const takeStates = findTakeStates(ops, opStates);
let done = false;
const innerStack: InnerFrame[] = [];
const closeInnerIterators = async () => {
for (const frame of innerStack) {
if (frame.type === 'flatMap') {
await frame.iterator.return?.();
}
}
innerStack.length = 0;
};
const handleResult = async (result: ProcessResult): Promise<IteratorResult<unknown, void> | null> => {
if (result.done) {
done = true;
await closeInnerIterators();
await iterator.return?.();
return { value: undefined, done: true };
}
if (result.expandIterator) {
innerStack.push({ type: 'expand', iterator: result.expandIterator, opIndex: result.expandOpIndex });
return null;
}
if (result.flatMapIterator) {
innerStack.push({ type: 'flatMap', iterator: result.flatMapIterator, opIndex: result.flatMapOpIndex });
return null;
}
if (result.shouldYield) {
return { value: result.value, done: false };
}
return null;
};
return {
async next(): Promise<IteratorResult<unknown, void>> {
while (!done) {
if (innerStack.length > 0) {
const frame = innerStack[innerStack.length - 1];
if (frame.type === 'expand') {
const expandResult = frame.iterator.next();
if (!expandResult.done) {
const result = await processOps(expandResult.value, ops, opStates, frame.opIndex + 1);
const handled = await handleResult(result);
if (handled) return handled;
continue;
}
innerStack.pop();
continue;
} else {
const flatMapResult = await frame.iterator.next();
if (!flatMapResult.done) {
const result = await processOps(flatMapResult.value, ops, opStates, frame.opIndex + 1);
const handled = await handleResult(result);
if (handled) return handled;
continue;
}
innerStack.pop();
continue;
}
}
if (checkTakeExhausted(takeStates)) {
done = true;
await iterator.return?.();
return { value: undefined, done: true };
}
const sourceResult = await iterator.next();
if (sourceResult.done) {
done = true;
return { value: undefined, done: true };
}
const result = await processOps(sourceResult.value, ops, opStates, 0);
const handled = await handleResult(result);
if (handled) return handled;
}
return { value: undefined, done: true };
},
async return(returnValue?: unknown): Promise<IteratorResult<unknown, void>> {
done = true;
await closeInnerIterators();
await iterator.return?.(returnValue);
return { value: undefined, done: true };
},
async throw(error?: unknown): Promise<IteratorResult<unknown, void>> {
done = true;
await closeInnerIterators();
if (iterator.throw) {
await iterator.throw(error);
}
throw error;
},
};
},
};
}
function createFusedIterable(iter: AsyncIteratorObject<unknown, unknown, unknown>): AsyncIterable<unknown, void, unknown> {
const source = getSource(iter);
const ops = iter.cachedOps ?? (iter.cachedOps = collectOps(iter));
if (ops.length === 0) {
return source as AsyncIterable<unknown, void, unknown>;
}
return hasExpandingOps(ops) ? createExpandingIterable(source, ops) : createSimpleIterable(source, ops);
}
/**
* A wrapper class providing functional operations on async iterables.
* Enables lazy evaluation and chainable transformations on async data streams.
*
* Key characteristics:
* - Lazy evaluation - operations are not executed until iteration begins
* - Chainable - all transformation methods return new AsyncIteratorObject instances
* - Supports both sync and async transformation functions
* - Memory efficient - processes values one at a time
* - Operation fusion - chains execute in optimized passes
*
* @template T The type of values yielded by the iterator
* @template TReturn The return type of the iterator
* @template TNext The type of value that can be passed to next()
*
* @example
* ```typescript
* // Create from an async generator
* async function* numbers() {
* yield 1; yield 2; yield 3;
* }
*
* const iterator = new AsyncIteratorObject(numbers())
* .map(x => x * 2)
* .filter(x => x > 2);
*
* for await (const value of iterator) {
* console.log(value); // 4, 6
* }
* ```
*/
export class AsyncIteratorObject<T, TReturn, TNext> {
/**
* Creates an AsyncIteratorObject from a synchronous iterable.
* Converts the sync iterable to async for uniform handling.
*
* @param iterable A synchronous iterable to convert
* @returns A new AsyncIteratorObject wrapping the converted iterable
*
* @example
* ```typescript
* const syncArray = [1, 2, 3, 4, 5];
* const asyncIterator = AsyncIteratorObject.from(syncArray);
*
* for await (const value of asyncIterator) {
* console.log(value); // 1, 2, 3, 4, 5
* }
* ```
*/
static from<T, TReturn, TNext>(iterable: Iterable<T, TReturn, TNext>): AsyncIteratorObject<T, TReturn, TNext> {
const asyncIterable = toAsyncIterable(iterable);
return new AsyncIteratorObject<T, TReturn, TNext>(asyncIterable);
}
/**
* Merges multiple async iterables into a single stream.
* Values from all sources are interleaved as they become available.
* The merged iterator completes when all source iterators complete.
*
* @param iterables The async iterables to merge
* @returns A new AsyncIteratorObject yielding values from all sources
*
* @example
* ```typescript
* async function* source1() { yield 1; yield 3; }
* async function* source2() { yield 2; yield 4; }
*
* const merged = AsyncIteratorObject.merge(source1(), source2());
*
* for await (const value of merged) {
* console.log(value); // Order depends on timing: 1, 2, 3, 4 or similar
* }
* ```
*/
static merge<T>(...iterables: AsyncIterable<T, void, unknown>[]): AsyncIteratorObject<T, void, unknown> {
return new AsyncIteratorObject<T, void, unknown>(mergeIterables(...iterables));
}
/** @internal */
iterable: AsyncIterable<unknown, unknown, unknown>;
/** @internal */
parent: AsyncIteratorObject<unknown, unknown, unknown> | null;
/** @internal */
op: FusedOp | null;
/** @internal */
cachedOps: FusedOp[] | null = null;
readonly [Symbol.toStringTag] = 'AsyncIteratorObject';
constructor(iterable: AsyncIterable<T, TReturn, TNext>, parent: AsyncIteratorObject<unknown, unknown, unknown> | null = null, op: FusedOp | null = null) {
this.iterable = iterable as AsyncIterable<unknown, unknown, unknown>;
this.parent = parent;
this.op = op;
}
/**
* Escape hatch for custom transformations not covered by the built-in operators.
* Materializes the fused operation chain, then applies a generator function to each value.
*
* @param generatorFactory A function that returns a generator function for transforming values
* @param signal Optional AbortSignal to cancel the operation
* @returns A new AsyncIteratorObject with transformed values
*/
pipe<U>(generatorFactory: () => (value: T) => AnyIterable<U, void, unknown>, signal?: AbortSignal): AsyncIteratorObject<U, void, unknown> {
const materialized = createFusedIterable(this as AsyncIteratorObject<unknown, unknown, unknown>);
const generator = pipe(materialized as AsyncIterable<T>, generatorFactory, signal);
return new AsyncIteratorObject<U, void, unknown>(generator);
}
/**
* Resolves promise-like values from the source iterator.
* Useful for normalizing values before applying type-guard predicates.
*
* @returns A new AsyncIteratorObject yielding awaited values
*/
awaited(): AsyncIteratorObject<Awaited<T>, void, unknown> {
return new AsyncIteratorObject<Awaited<T>, void, unknown>(
this.iterable as AsyncIterable<Awaited<T>, void, unknown>,
this as AsyncIteratorObject<unknown, unknown, unknown>,
{ kind: OpKind.AWAITED },
);
}
/**
* Transforms each value using a mapping function.
* The callback can be synchronous or return a promise.
*
* @param callbackfn Function to transform each value
* @returns A new AsyncIteratorObject yielding transformed values
*
* @example
* ```typescript
* const numbers = AsyncIteratorObject.from([1, 2, 3]);
* const doubled = numbers.map(x => x * 2);
*
* for await (const value of doubled) {
* console.log(value); // 2, 4, 6
* }
* ```
*/
map<U>(callbackfn: (value: T, index: number) => U): AsyncIteratorObject<U, void, unknown> {
return new AsyncIteratorObject<U, void, unknown>(this.iterable as AsyncIterable<U, void, unknown>, this as AsyncIteratorObject<unknown, unknown, unknown>, {
kind: OpKind.MAP,
fn: callbackfn as (value: unknown, index: number) => unknown,
});
}
/**
* Filters values based on a predicate function.
* Only values for which the predicate returns truthy are yielded.
* Supports type guard predicates for type narrowing.
*
* @param predicate Function to test each value
* @returns A new AsyncIteratorObject yielding only values that pass the test
*
* @example
* ```typescript
* const numbers = AsyncIteratorObject.from([1, 2, 3, 4, 5]);
* const evens = numbers.filter(x => x % 2 === 0);
*
* for await (const value of evens) {
* console.log(value); // 2, 4
* }
* ```
*/
filter(predicate: (value: T, index: number) => unknown): AsyncIteratorObject<T, void, unknown>;
filter<S extends T>(predicate: (value: T, index: number) => value is S): AsyncIteratorObject<S, void, unknown>;
filter<S extends T>(predicate: (value: T, index: number) => value is S): AsyncIteratorObject<S, void, unknown> {
return new AsyncIteratorObject<S, void, unknown>(this.iterable as AsyncIterable<S, void, unknown>, this as AsyncIteratorObject<unknown, unknown, unknown>, {
kind: OpKind.FILTER,
fn: predicate as (value: unknown, index: number) => unknown,
});
}
/**
* Combined filter and map operation. Returns undefined to skip a value.
* The callback result is awaited to check for undefined.
*
* @param callbackfn Function that returns a transformed value or undefined to skip
* @returns A new AsyncIteratorObject yielding non-undefined transformed values
*
* @example
* ```typescript
* const numbers = AsyncIteratorObject.from([1, 2, 3, 4, 5]);
* const doubledEvens = numbers.filterMap(x => x % 2 === 0 ? x * 2 : undefined);
*
* for await (const value of doubledEvens) {
* console.log(value); // 4, 8
* }
* ```
*/
filterMap<U>(callbackfn: (value: T, index: number) => U): AsyncIteratorObject<Exclude<Awaited<U>, undefined>, void, unknown> {
return new AsyncIteratorObject<Exclude<Awaited<U>, undefined>, void, unknown>(
this.iterable as AsyncIterable<Exclude<Awaited<U>, undefined>, void, unknown>,
this as AsyncIteratorObject<unknown, unknown, unknown>,
{ kind: OpKind.FILTER_MAP, fn: callbackfn as (value: unknown, index: number) => unknown },
);
}
/**
* Executes a side-effect function for each value without modifying the stream.
* Useful for debugging or logging. The callback is awaited for proper sequencing.
*
* @param callbackfn Function to execute for each value
* @returns A new AsyncIteratorObject yielding the same values
*
* @example
* ```typescript
* const numbers = AsyncIteratorObject.from([1, 2, 3]);
* const logged = numbers.inspect(x => console.log('value:', x)).map(x => x * 2);
* ```
*/
inspect(callbackfn: (value: T, index: number) => unknown): AsyncIteratorObject<T, TReturn, TNext> {
return new AsyncIteratorObject<T, TReturn, TNext>(
this.iterable as AsyncIterable<T, TReturn, TNext>,
this as AsyncIteratorObject<unknown, unknown, unknown>,
{ kind: OpKind.INSPECT, fn: callbackfn as (value: unknown, index: number) => unknown },
);
}
/**
* Wraps each value with its index as a tuple.
* Useful after filtering when original indices are lost.
*
* @param start Starting index (default: 0)
* @returns A new AsyncIteratorObject yielding [index, value] tuples
*
* @example
* ```typescript
* const letters = AsyncIteratorObject.from(['a', 'b', 'c']);
* const enumerated = letters.enumerate();
*
* for await (const [i, v] of enumerated) {
* console.log(i, v); // 0 'a', 1 'b', 2 'c'
* }
* ```
*/
enumerate(start: number = 0): AsyncIteratorObject<[number, T], void, unknown> {
return new AsyncIteratorObject<[number, T], void, unknown>(
this.iterable as AsyncIterable<[number, T], void, unknown>,
this as AsyncIteratorObject<unknown, unknown, unknown>,
{ kind: OpKind.ENUMERATE, start },
);
}
/**
* Creates an iterator whose values are the values from this iterator, stopping once the provided limit is reached.
* @param limit The maximum number of values to yield.
*/
take(limit: number): AsyncIteratorObject<T, void, unknown> {
return new AsyncIteratorObject<T, void, unknown>(this.iterable as AsyncIterable<T, void, unknown>, this as AsyncIteratorObject<unknown, unknown, unknown>, {
kind: OpKind.TAKE,
limit,
});
}
/**
* Takes values while the predicate returns truthy.
* Stops immediately when predicate returns falsy.
* Supports type guard predicates for type narrowing.
*
* @param predicate Function to test each value
* @returns A new AsyncIteratorObject yielding values until predicate fails
*
* @example
* ```typescript
* const numbers = AsyncIteratorObject.from([1, 2, 3, 4, 5]);
* const small = numbers.takeWhile(x => x < 4);
*
* for await (const value of small) {
* console.log(value); // 1, 2, 3
* }
* ```
*/
takeWhile(predicate: (value: T, index: number) => unknown): AsyncIteratorObject<T, void, unknown>;
takeWhile<S extends T>(predicate: (value: T, index: number) => value is S): AsyncIteratorObject<S, void, unknown>;
takeWhile<S extends T>(predicate: (value: T, index: number) => value is S): AsyncIteratorObject<S, void, unknown> {
return new AsyncIteratorObject<S, void, unknown>(this.iterable as AsyncIterable<S, void, unknown>, this as AsyncIteratorObject<unknown, unknown, unknown>, {
kind: OpKind.TAKE_WHILE,
fn: predicate as (value: unknown, index: number) => unknown,
});
}
/**
* Creates an iterator whose values are the values from this iterator after skipping the provided count.
* @param count The number of values to drop.
*/
drop(count: number): AsyncIteratorObject<T, void, unknown> {
return new AsyncIteratorObject<T, void, unknown>(this.iterable as AsyncIterable<T, void, unknown>, this as AsyncIteratorObject<unknown, unknown, unknown>, {
kind: OpKind.DROP,
count,
});
}
/**
* Skips values while the predicate returns truthy.
* Yields all remaining values once predicate returns falsy.
*
* @param predicate Function to test each value
* @returns A new AsyncIteratorObject skipping values until predicate fails
*
* @example
* ```typescript
* const numbers = AsyncIteratorObject.from([1, 2, 3, 4, 5]);
* const afterSmall = numbers.dropWhile(x => x < 3);
*
* for await (const value of afterSmall) {
* console.log(value); // 3, 4, 5
* }
* ```
*/
dropWhile(predicate: (value: T, index: number) => unknown): AsyncIteratorObject<T, void, unknown> {
return new AsyncIteratorObject<T, void, unknown>(this.iterable as AsyncIterable<T, void, unknown>, this as AsyncIteratorObject<unknown, unknown, unknown>, {
kind: OpKind.DROP_WHILE,
fn: predicate as (value: unknown, index: number) => unknown,
});
}
/**
* Creates an iterator whose values are the result of applying the callback to the values from this iterator and then flattening the resulting iterators or iterables.
* @param callback A function that accepts up to two arguments to be used to transform values from the underlying iterator into new iterators or iterables to be flattened into the result.
*/
flatMap<U>(callback: (value: T, index: number) => AsyncIterable<U, void, unknown>): AsyncIteratorObject<U, void, unknown> {
return new AsyncIteratorObject<U, void, unknown>(this.iterable as AsyncIterable<U, void, unknown>, this as AsyncIteratorObject<unknown, unknown, unknown>, {
kind: OpKind.FLAT_MAP,
fn: callback as (value: unknown, index: number) => AsyncIterable<unknown, void, unknown>,
});
}
/**
* Creates an iterator of accumulated values by applying a reducer function.
* Unlike Array.reduce, this returns an iterator that yields each intermediate accumulated value,
* not just the final result. This allows observing the accumulation process.
*
* @param callbackfn Reducer function to accumulate values
* @param initialValue Optional initial value for the accumulation
* @returns A new AsyncIteratorObject yielding accumulated values at each step
*
* @example
* ```typescript
* const numbers = AsyncIteratorObject.from([1, 2, 3, 4]);
* const sums = numbers.reduce((sum, x) => sum + x, 0);
*
* for await (const value of sums) {
* console.log(value); // 1, 3, 6, 10 (running totals)
* }
* ```
*/
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number) => T): AsyncIteratorObject<T, void, unknown>;
reduce<R>(callbackfn: (previousValue: R, currentValue: T, currentIndex: number) => R, initialValue: R): AsyncIteratorObject<R, void, unknown>;
reduce<R>(callbackfn: (previousValue: R, currentValue: T, currentIndex: number) => R, ...args: unknown[]): AsyncIteratorObject<R, void, unknown> {
const hasInit = args.length > 0;
return new AsyncIteratorObject<R, void, unknown>(this.iterable as AsyncIterable<R, void, unknown>, this as AsyncIteratorObject<unknown, unknown, unknown>, {
kind: OpKind.REDUCE,
fn: callbackfn as (acc: unknown, value: unknown, index: number) => unknown,
init: args[0],
hasInit,
});
}
/**
* Transforms each value into multiple values using an expander function.
* Each input value is expanded into zero or more output values.
* Like `flatMap` but takes sync Iterables (or Promises of Iterables) instead of AsyncIterables.
*
* @param callbackfn Function that returns an iterable of values for each input
* @returns A new AsyncIteratorObject yielding all expanded values
*
* @example
* ```typescript
* const numbers = AsyncIteratorObject.from([1, 2, 3]);
* const expanded = numbers.expand(x => [x, x * 10]);
*
* for await (const value of expanded) {
* console.log(value); // 1, 10, 2, 20, 3, 30
* }
* ```
*/
expand<U>(callbackfn: (value: T, index: number) => Promise<Iterable<U>> | Iterable<U>): AsyncIteratorObject<U, void, unknown> {
return new AsyncIteratorObject<U, void, unknown>(this.iterable as AsyncIterable<U, void, unknown>, this as AsyncIteratorObject<unknown, unknown, unknown>, {
kind: OpKind.EXPAND,
fn: callbackfn as (value: unknown, index: number) => Iterable<unknown> | Promise<Iterable<unknown>>,
});
}
[Symbol.asyncIterator](): AsyncIterator<T, TReturn, TNext> {
return createFusedIterable(this as AsyncIteratorObject<unknown, unknown, unknown>)[Symbol.asyncIterator]() as AsyncIterator<T, TReturn, TNext>;
}
}