Skip to content

@absoluteui/helpers/linq

Lazy LINQ: pipeable operators and a fluent Enumerable with the .NET surface.

import { ... } from '@absoluteui/helpers/linq';

LINQ-style queries over any iterable: filter, project, join, group, and aggregate in one readable chain. Queries are lazy (nothing runs until a terminal such as toArray() or sum()), so they work on generators and large sequences. Use the fluent from(...) style, or the standalone operators with pipe when bundle size matters. For one-off array tasks, helpers/array is simpler; the Helpers and services guide covers the shared conventions.

How do I build a headcount and payroll report per department?

Section titled “How do I build a headcount and payroll report per department?”

Join employees to departments, group by department name, and aggregate each group; orderByDescending sorts the report.

import { from } from '@absoluteui/helpers/linq';
const departments = [{ id: 1, name: 'Finance' }, { id: 2, name: 'HR' }, { id: 3, name: 'IT' }];
const employees = [
{ name: 'Asha', deptId: 1, salary: 5200, active: true },
{ name: 'Ben', deptId: 2, salary: 4100, active: true },
{ name: 'Chen', deptId: 1, salary: 6100, active: true },
{ name: 'Dana', deptId: 3, salary: 7000, active: false },
];
const report = from(employees)
.where((e) => e.active)
.join(departments, (e) => e.deptId, (d) => d.id, (e, d) => ({ dept: d.name, salary: e.salary }))
.groupBy((r) => r.dept)
.select((g) => ({ dept: g.key, headcount: g.count(), payroll: g.sum((r) => r.salary) }))
.orderByDescending((r) => r.payroll)
.toArray();
report; // => [{ dept: 'Finance', headcount: 2, payroll: 11300 }, { dept: 'HR', headcount: 1, payroll: 4100 }]

leftJoin passes undefined for a missing partner, so employees without a manager still appear; groupJoin attaches all matches as a list, for example each manager’s direct reports.

import { from } from '@absoluteui/helpers/linq';
const people = [{ id: 'E-1', name: 'Maya', managerId: null }, { id: 'E-2', name: 'Omar', managerId: 'E-1' }, { id: 'E-3', name: 'Lin', managerId: 'E-1' }];
from(people)
.leftJoin(people, (p) => p.managerId, (m) => m.id, (p, m) => `${p.name}: ${m?.name ?? 'no manager'}`)
.toArray(); // => ['Maya: no manager', 'Omar: Maya', 'Lin: Maya']
from(people)
.groupJoin(people, (m) => m.id, (p) => p.managerId, (m, reports) => ({ manager: m.name, reports: reports.count() }))
.where((r) => r.reports > 0)
.toArray(); // => [{ manager: 'Maya', reports: 2 }]

How do I stop early on a large or endless sequence?

Section titled “How do I stop early on a large or endless sequence?”

Because queries are lazy, take, first, and any stop pulling items as soon as they have an answer; only the items needed are produced.

import { from } from '@absoluteui/helpers/linq';
let produced = 0;
function* payDates(): Generator<string> {
for (let month = 1; ; month++) {
produced++;
yield `2026-${String(month).padStart(2, '0')}-25`;
}
}
from(payDates()).skip(9).take(2).toArray(); // => ['2026-10-25', '2026-11-25']
produced; // => 11

How do I use standalone operators to keep bundles small?

Section titled “How do I use standalone operators to keep bundles small?”

pipe applies operator functions left to right; import only the operators you use.

import { orderBy, pipe, select, take, thenBy, toArray, where } from '@absoluteui/helpers/linq';
const requests = [
{ who: 'Ben', days: 5, type: 'annual' },
{ who: 'Asha', days: 2, type: 'sick' },
{ who: 'Chen', days: 5, type: 'annual' },
{ who: 'Dana', days: 1, type: 'annual' },
];
pipe(
requests,
where((r) => r.type === 'annual'),
orderBy((r) => -r.days),
thenBy((r) => r.who),
take(2),
select((r) => r.who),
toArray(),
); // => ['Ben', 'Chen']

first, single, and elementAt throw on missing or ambiguous results; use the ...OrDefault variants when absence is normal.

import { from } from '@absoluteui/helpers/linq';
const ids = from(['E-1', 'E-2']);
ids.firstOrDefault((id) => id === 'E-9', 'none'); // => 'none'
ids.first((id) => id === 'E-9'); // => throws HLP_LINQ_NO_ELEMENTS
ids.single(); // => throws HLP_LINQ_MULTIPLE_ELEMENTS

empty

An empty sequence.

function empty<T>(): Enumerable<T>;

Example:

empty<number>().count(); // => 0

from

Wraps any iterable in a lazy, fluent Enumerable.

function from<T>(source: Iterable<T>): Enumerable<T>;

Example:

const users = [
{ name: 'Ben', email: 'ben@x.io', active: true },
{ name: 'Cy', email: 'cy@x.io', active: false },
{ name: 'Ada', email: 'ada@x.io', active: true },
];
from(users).where((u) => u.active).orderBy((u) => u.name).select((u) => u.email).toArray(); // => ['ada@x.io', 'ben@x.io']

range

count consecutive integers from start (lazy).

function range(start: number, count: number): Enumerable<number>;

Example:

range(1, 3).toArray(); // => [1, 2, 3]

repeat

value repeated count times (lazy).

function repeat<T>(value: T, count: number): Enumerable<T>;

Example:

repeat('x', 2).toArray(); // => ['x', 'x']

Enumerable

Lazy, re-iterable sequence with the .NET LINQ surface.

class Enumerable<T> implements Iterable<T> {
[Symbol.iterator](): Iterator<T>;
pipe<A>(op1: (source: Iterable<T>) => A): A;
pipe<A, B>(op1: (source: Iterable<T>) => A, op2: (a: A) => B): B;
pipe<A, B, C>(op1: (source: Iterable<T>) => A, op2: (a: A) => B, op3: (b: B) => C): C;
pipe<A, B, C, D>(op1: (source: Iterable<T>) => A, op2: (a: A) => B, op3: (b: B) => C, op4: (c: C) => D): D;
where<S extends T>(predicate: (item: T, index: number) => item is S): Enumerable<S>;
where(predicate: Predicate<T>): Enumerable<T>;
whereType<S extends T>(guard: (item: T) => item is S): Enumerable<S>;
select<U>(selector: (item: T, index: number) => U): Enumerable<U>;
selectMany<U>(selector: (item: T, index: number) => Iterable<U>): Enumerable<U>;
selectMany<U, R>(selector: (item: T, index: number) => Iterable<U>, result: (item: T, child: U) => R): Enumerable<R>;
orderBy<K>(key: (item: T) => K, compare?: KeyComparer<K>): OrderedEnumerable<T>;
orderByDescending<K>(key: (item: T) => K, compare?: KeyComparer<K>): OrderedEnumerable<T>;
reverse(): Enumerable<T>;
groupBy<K>(key: (item: T) => K): Enumerable<EnumerableGrouping<K, T>>;
groupBy<K, E>(key: (item: T) => K, element: (item: T) => E): Enumerable<EnumerableGrouping<K, E>>;
groupBy<K, E, R>(key: (item: T) => K, element: (item: T) => E, result: (key: K, items: Enumerable<E>) => R): Enumerable<R>;
join<U, K, R>(inner: Iterable<U>, outerKey: (item: T) => K, innerKey: (item: U) => K, result: (outer: T, inner: U) => R): Enumerable<R>;
groupJoin<U, K, R>(inner: Iterable<U>, outerKey: (item: T) => K, innerKey: (item: U) => K, result: (outer: T, inners: Enumerable<U>) => R): Enumerable<R>;
leftJoin<U, K, R>(inner: Iterable<U>, outerKey: (item: T) => K, innerKey: (item: U) => K, result: (outer: T, inner: U | undefined) => R): Enumerable<R>;
distinct(): Enumerable<T>;
distinctBy(key: (item: T) => unknown): Enumerable<T>;
union(other: Iterable<T>): Enumerable<T>;
unionBy(other: Iterable<T>, key: (item: T) => unknown): Enumerable<T>;
intersect(other: Iterable<T>): Enumerable<T>;
intersectBy(other: Iterable<T>, key: (item: T) => unknown): Enumerable<T>;
except(other: Iterable<T>): Enumerable<T>;
exceptBy(other: Iterable<T>, key: (item: T) => unknown): Enumerable<T>;
concat(other: Iterable<T>): Enumerable<T>;
zip<U>(other: Iterable<U>): Enumerable<[T, U]>;
zip<U, R>(other: Iterable<U>, result: (a: T, b: U) => R): Enumerable<R>;
append(item: T): Enumerable<T>;
prepend(item: T): Enumerable<T>;
defaultIfEmpty(value: T): Enumerable<T>;
chunk(size: number): Enumerable<T[]>;
take(count: number): Enumerable<T>;
takeWhile(predicate: Predicate<T>): Enumerable<T>;
takeLast(count: number): Enumerable<T>;
skip(count: number): Enumerable<T>;
skipWhile(predicate: Predicate<T>): Enumerable<T>;
skipLast(count: number): Enumerable<T>;
first(predicate?: Predicate<T>): T;
firstOrDefault(predicate?: Predicate<T>): T | undefined;
firstOrDefault<D>(predicate: Predicate<T> | undefined, defaultValue: D): T | D;
last(predicate?: Predicate<T>): T;
lastOrDefault(predicate?: Predicate<T>): T | undefined;
lastOrDefault<D>(predicate: Predicate<T> | undefined, defaultValue: D): T | D;
single(predicate?: Predicate<T>): T;
singleOrDefault(predicate?: Predicate<T>): T | undefined;
singleOrDefault<D>(predicate: Predicate<T> | undefined, defaultValue: D): T | D;
elementAt(index: number): T;
elementAtOrDefault(index: number): T | undefined;
elementAtOrDefault<D>(index: number, defaultValue: D): T | D;
any(predicate?: Predicate<T>): boolean;
all(predicate: Predicate<T>): boolean;
contains(value: T, equals?: EqualityComparer<T>): boolean;
sequenceEqual(other: Iterable<T>, equals?: EqualityComparer<T>): boolean;
count(predicate?: Predicate<T>): number;
sum(this: Enumerable<number>): number;
sum(selector: (item: T, index: number) => number): number;
average(this: Enumerable<number>): number;
average(selector: (item: T, index: number) => number): number;
min(): T;
min<R>(selector: (item: T, index: number) => R, compare?: KeyComparer<R>): R;
max(): T;
max<R>(selector: (item: T, index: number) => R, compare?: KeyComparer<R>): R;
minBy<K>(key: (item: T) => K, compare?: KeyComparer<K>): T | undefined;
maxBy<K>(key: (item: T) => K, compare?: KeyComparer<K>): T | undefined;
aggregate(fn: (acc: T, item: T, index: number) => T): T;
aggregate<A>(seed: A, fn: (acc: A, item: T, index: number) => A): A;
aggregate<A, R>(seed: A, fn: (acc: A, item: T, index: number) => A, result: (acc: A) => R): R;
toArray(): T[];
toSet(): Set<T>;
toMap<K>(key: (item: T) => K): Map<K, T>;
toMap<K, V>(key: (item: T) => K, value: (item: T) => V): Map<K, V>;
toDictionary<K>(key: (item: T) => K): Map<K, T>;
toDictionary<K, V>(key: (item: T) => K, value: (item: T) => V): Map<K, V>;
toLookup<K>(key: (item: T) => K): Lookup<K, T>;
toLookup<K, V>(key: (item: T) => K, value: (item: T) => V): Lookup<K, V>;
forEach(action: (item: T, index: number) => void): void;
}

Example:

const big = from([1, 2, 3]).where((n) => n > 1);
[...big]; // => [2, 3]
big.count(); // => 2 // re-iterable

EnumerableGrouping

A group from Enumerable#groupBy: an Enumerable of the members with their key.

class EnumerableGrouping<K, T> extends Enumerable<T> {
readonly key: K;
}

Example:

const users = [{ name: 'Ada', dept: 'HR' }, { name: 'Ben', dept: 'IT' }, { name: 'Cy', dept: 'HR' }];
from(users).groupBy((u) => u.dept).select((g) => ({ dept: g.key, n: g.count() })).toArray(); // => [{ dept: 'HR', n: 2 }, { dept: 'IT', n: 1 }]

OrderedEnumerable

Result of orderBy/orderByDescending; the only type exposing thenBy.

class OrderedEnumerable<T> extends Enumerable<T> {
thenBy<K>(key: (item: T) => K, compare?: KeyComparer<K>): OrderedEnumerable<T>;
thenByDescending<K>(key: (item: T) => K, compare?: KeyComparer<K>): OrderedEnumerable<T>;
}

Example:

const rows = [{ dept: 'IT', salary: 50 }, { dept: 'HR', salary: 40 }, { dept: 'IT', salary: 70 }];
from(rows).orderBy((r) => r.dept).thenByDescending((r) => r.salary).select((r) => r.salary).toArray(); // => [40, 70, 50]

aggregate

Folds the sequence. Without a seed the first item seeds the fold (throws HLP_LINQ_NO_ELEMENTS when empty).

function aggregate<T>(fn: (acc: T, item: T, index: number) => T): Terminal<T, T>;
function aggregate<T, A>(seed: A, fn: (acc: A, item: T, index: number) => A): Terminal<T, A>;
function aggregate<T, A, R>(seed: A, fn: (acc: A, item: T, index: number) => A, result: (acc: A) => R): Terminal<T, R>;

Example:

pipe([1, 2, 3], aggregate((a, b) => a + b)); // => 6
pipe([1, 2, 3], aggregate('', (s, n) => s + n)); // => '123'
pipe([1, 2], aggregate(0, (a, n) => a + n, (a) => a * 10)); // => 30

all

True when every item matches (true for empty); stops at the first failure.

function all<T>(predicate: Predicate<T>): Terminal<T, boolean>;

Example:

pipe([1, 2], all((n) => n > 0)); // => true

any

True when any item matches (or the sequence is non-empty); stops at the first match.

function any<T>(predicate?: Predicate<T>): Terminal<T, boolean>;

Example:

pipe([1, 2], any((n) => n > 1)); // => true

append

Adds an item at the end.

function append<T>(item: T): Operator<T, T>;

Example:

pipe([1], append(2), toArray()); // => [1, 2]

assertCount

Throws HLP_LINQ_INVALID_ARGUMENT unless n is a non-negative safe integer.

function assertCount(n: number): void;

Example:

assertCount(3); // => undefined
assertCount(-1); // => throws HLP_LINQ_INVALID_ARGUMENT

average

Arithmetic mean; throws HLP_LINQ_NO_ELEMENTS for empty input.

function average(): Terminal<number, number>;
function average<T>(selector: (item: T, index: number) => number): Terminal<T, number>;

Example:

pipe([1, 2, 3], average()); // => 2

chunk

Splits into arrays of size (the last may be shorter).

function chunk<T>(size: number): Operator<T, T[]>;

Example:

pipe([1, 2, 3], chunk(2), toArray()); // => [[1, 2], [3]]

concat

Appends another sequence.

function concat<T>(other: Iterable<T>): Operator<T, T>;

Example:

pipe([1], concat([2, 3]), toArray()); // => [1, 2, 3]

contains

True when the sequence contains value (SameValueZero by default).

function contains<T>(value: T, equals?: EqualityComparer<T>): Terminal<T, boolean>;

Example:

pipe([1, NaN], contains(NaN)); // => true

count

Number of matching items (O(1) for arrays, Sets and Maps without a predicate).

function count<T>(predicate?: Predicate<T>): Terminal<T, number>;

Example:

pipe([1, 2, 3], count((n) => n > 1)); // => 2

defaultCompare

Default key order: nullish/NaN first, then booleans, numbers and bigints, strings (code-unit order), dates.

function defaultCompare(a: unknown, b: unknown): number;

Example:

[3, null, 1].sort(defaultCompare); // => [null, 1, 3]

defaultIfEmpty

Yields value alone when the source is empty.

function defaultIfEmpty<T>(value: T): Operator<T, T>;

Example:

pipe([], defaultIfEmpty(0), toArray()); // => [0]

distinct

Removes duplicates (SameValueZero), keeping first occurrences.

function distinct<T>(): Operator<T, T>;

Example:

pipe([1, 1, 2], distinct(), toArray()); // => [1, 2]

distinctBy

Removes items with a duplicate key, keeping first occurrences.

function distinctBy<T>(key: (item: T) => unknown): Operator<T, T>;

Example:

const users = [{ id: 1, email: 'ada@x.io' }, { id: 2, email: 'ben@x.io' }, { id: 3, email: 'ada@x.io' }];
pipe(users, distinctBy((u) => u.email), toArray()).map((u) => u.id); // => [1, 2]

elementAt

Item at index (negative counts from the end); throws HLP_LINQ_INDEX_OUT_OF_RANGE.

function elementAt<T>(index: number): Terminal<T, T>;

Example:

pipe([1, 2, 3], elementAt(-1)); // => 3

elementAtOrDefault

Item at index (negative counts from the end), or defaultValue.

function elementAtOrDefault<T>(index: number): Terminal<T, T | undefined>;
function elementAtOrDefault<T, D>(index: number, defaultValue: D): Terminal<T, T | D>;

Example:

pipe([1], elementAtOrDefault(5)); // => undefined

except

Distinct items of the source that do not occur in other.

function except<T>(other: Iterable<T>): Operator<T, T>;

Example:

pipe([1, 2, 3, 1], except([2]), toArray()); // => [1, 3]

exceptBy

Items whose key does not occur among the keys of other, distinct by key.

function exceptBy<T>(other: Iterable<T>, key: (item: T) => unknown): Operator<T, T>;

Example:

const a = [{ id: 1, v: 'a' }, { id: 2, v: 'a' }];
const b = [{ id: 2, v: 'b' }, { id: 3, v: 'b' }];
pipe(a, exceptBy(b, (x) => x.id), toArray()); // => [{ id: 1, v: 'a' }]

first

First matching item; throws HLP_LINQ_NO_ELEMENTS when none.

function first<T>(predicate?: Predicate<T>): Terminal<T, T>;

Example:

pipe([1, 2, 3], first((n) => n > 1)); // => 2

firstOrDefault

First matching item, or defaultValue (default undefined).

function firstOrDefault<T>(predicate?: Predicate<T>): Terminal<T, T | undefined>;
function firstOrDefault<T, D>(predicate: Predicate<T> | undefined, defaultValue: D): Terminal<T, T | D>;

Example:

pipe([1], firstOrDefault((n) => n > 5)); // => undefined
pipe([1], firstOrDefault(undefined, 0)); // => 1

forEach

Runs action(item, index) for every item.

function forEach<T>(action: (item: T, index: number) => void): Terminal<T, void>;

Example:

let total = 0;
pipe([1, 2], forEach((n) => { total += n; }));
total; // => 3

groupBy

Groups by key (SameValueZero) in first-key order; materializes when iterated.

function groupBy<T, K>(key: (item: T) => K): Operator<T, Grouping<K, T>>;
function groupBy<T, K, E>(key: (item: T) => K, element: (item: T) => E): Operator<T, Grouping<K, E>>;
function groupBy<T, K, E, R>(key: (item: T) => K, element: (item: T) => E, result: (key: K, items: readonly E[]) => R): Operator<T, R>;

Example:

pipe(['a', 'bb', 'cc'], groupBy((s) => s.length), select((g) => [g.key, g.items.length]), toArray()); // => [[1, 1], [2, 2]]

groupJoin

Pairs each outer item with all matching inner items (possibly none).

function groupJoin<T, U, K, R>(inner: Iterable<U>, outerKey: (item: T) => K, innerKey: (item: U) => K, result: (outer: T, inners: readonly U[]) => R): Operator<T, R>;

Example:

const customers = [{ id: 1, name: 'Acme' }, { id: 2, name: 'Globex' }];
const orders = [{ id: 10, customerId: 2 }, { id: 12, customerId: 2 }];
pipe(customers, groupJoin(orders, (c) => c.id, (o) => o.customerId, (c, os) => ({ name: c.name, n: os.length })), toArray()); // => [{ name: 'Acme', n: 0 }, { name: 'Globex', n: 2 }]

intersect

Distinct items of the source that also occur in other.

function intersect<T>(other: Iterable<T>): Operator<T, T>;

Example:

pipe([1, 2, 3, 2], intersect([2, 3]), toArray()); // => [2, 3]

intersectBy

Items whose key occurs among the keys of other (the key selector applies to both sequences), distinct by key.

function intersectBy<T>(other: Iterable<T>, key: (item: T) => unknown): Operator<T, T>;

Example:

const a = [{ id: 1, v: 'a' }, { id: 2, v: 'a' }];
const b = [{ id: 2, v: 'b' }, { id: 3, v: 'b' }];
pipe(a, intersectBy(b, (x) => x.id), toArray()); // => [{ id: 2, v: 'a' }]

join

Inner equi-join; nullish keys never match. The inner sequence is read once per iteration.

function join<T, U, K, R>(inner: Iterable<U>, outerKey: (item: T) => K, innerKey: (item: U) => K, result: (outer: T, inner: U) => R): Operator<T, R>;

Example:

const customers = [{ id: 1, name: 'Acme' }, { id: 2, name: 'Globex' }];
const orders = [{ id: 10, customerId: 2 }, { id: 11, customerId: 3 }];
pipe(orders, join(customers, (o) => o.customerId, (c) => c.id, (o, c) => ({ ...o, name: c.name })), toArray()); // => [{ id: 10, customerId: 2, name: 'Globex' }]

last

Last matching item; throws HLP_LINQ_NO_ELEMENTS when none.

function last<T>(predicate?: Predicate<T>): Terminal<T, T>;

Example:

pipe([1, 2, 3], last()); // => 3

lastOrDefault

Last matching item, or defaultValue (default undefined).

function lastOrDefault<T>(predicate?: Predicate<T>): Terminal<T, T | undefined>;
function lastOrDefault<T, D>(predicate: Predicate<T> | undefined, defaultValue: D): Terminal<T, T | D>;

Example:

pipe([], lastOrDefault()); // => undefined

leftJoin

Left outer join: every outer item appears at least once, with undefined when nothing matches.

function leftJoin<T, U, K, R>(inner: Iterable<U>, outerKey: (item: T) => K, innerKey: (item: U) => K, result: (outer: T, inner: U | undefined) => R): Operator<T, R>;

Example:

const depts = [{ id: 'HR', name: 'Human Resources' }];
const employees = [{ name: 'Ada', deptId: 'HR' }, { name: 'Ben', deptId: 'IT' }];
pipe(employees, leftJoin(depts, (e) => e.deptId, (d) => d.id, (e, d) => [e.name, d?.name]), toArray()); // => [['Ada', 'Human Resources'], ['Ben', undefined]]

max

Largest value (nullish and NaN ignored); throws HLP_LINQ_NO_ELEMENTS when none remain.

function max<T>(): Terminal<T, T>;
function max<T, R>(selector: (item: T, index: number) => R, compare?: KeyComparer<R>): Terminal<T, R>;

Example:

pipe([3, 1, 2], max()); // => 3

maxBy

Item with the largest key (first on ties, nullish keys ignored), or undefined.

function maxBy<T, K>(key: (item: T) => K, compare?: KeyComparer<K>): Terminal<T, T | undefined>;

Example:

const users = [{ name: 'Ada', age: 36 }, { name: 'Ben', age: 29 }];
pipe(users, maxBy((u) => u.age)); // => { name: 'Ada', age: 36 }

min

Smallest value (nullish and NaN ignored); throws HLP_LINQ_NO_ELEMENTS when none remain.

function min<T>(): Terminal<T, T>;
function min<T, R>(selector: (item: T, index: number) => R, compare?: KeyComparer<R>): Terminal<T, R>;

Example:

pipe([3, 1, 2], min()); // => 1
const users = [{ name: 'Ada', age: 36 }, { name: 'Ben', age: 29 }];
pipe(users, min((u) => u.age)); // => 29

minBy

Item with the smallest key (first on ties, nullish keys ignored), or undefined.

function minBy<T, K>(key: (item: T) => K, compare?: KeyComparer<K>): Terminal<T, T | undefined>;

Example:

const users = [{ name: 'Ada', age: 36 }, { name: 'Ben', age: 29 }];
pipe(users, minBy((u) => u.age)); // => { name: 'Ben', age: 29 }

orderBy

Stable ascending sort by key (deferred until iterated).

function orderBy<T, K>(key: (item: T) => K, compare?: KeyComparer<K>): (source: Iterable<T>) => OrderedSequence<T>;

Example:

const people = [{ first: 'Ben', last: 'Kay' }, { first: 'Ada', last: 'Lee' }, { first: 'Al', last: 'Kay' }];
pipe(people, orderBy((p) => p.last), thenBy((p) => p.first), toArray()).map((p) => p.first); // => ['Al', 'Ben', 'Ada']

orderByDescending

Stable descending sort by key (deferred until iterated).

function orderByDescending<T, K>(key: (item: T) => K, compare?: KeyComparer<K>): (source: Iterable<T>) => OrderedSequence<T>;

Example:

pipe([1, 3, 2], orderByDescending((n) => n), toArray()); // => [3, 2, 1]

pipe

Threads source through operators left to right; the last may be a terminal.

function pipe<A>(source: A): A;
function pipe<A, B>(source: A, op1: (a: A) => B): B;
function pipe<A, B, C>(source: A, op1: (a: A) => B, op2: (b: B) => C): C;
function pipe<A, B, C, D>(source: A, op1: (a: A) => B, op2: (b: B) => C, op3: (c: C) => D): D;
function pipe<A, B, C, D, E>(source: A, op1: (a: A) => B, op2: (b: B) => C, op3: (c: C) => D, op4: (d: D) => E): E;
function pipe<A, B, C, D, E, F>(source: A, op1: (a: A) => B, op2: (b: B) => C, op3: (c: C) => D, op4: (d: D) => E, op5: (e: E) => F): F;
function pipe<A, B, C, D, E, F, G>(source: A, op1: (a: A) => B, op2: (b: B) => C, op3: (c: C) => D, op4: (d: D) => E, op5: (e: E) => F, op6: (f: F) => G): G;
function pipe<A, B, C, D, E, F, G, H>(source: A, op1: (a: A) => B, op2: (b: B) => C, op3: (c: C) => D, op4: (d: D) => E, op5: (e: E) => F, op6: (f: F) => G, op7: (g: G) => H): H;
function pipe<A, B, C, D, E, F, G, H, I>(source: A, op1: (a: A) => B, op2: (b: B) => C, op3: (c: C) => D, op4: (d: D) => E, op5: (e: E) => F, op6: (f: F) => G, op7: (g: G) => H, op8: (h: H) => I): I;
function pipe<A, B, C, D, E, F, G, H, I, J>(source: A, op1: (a: A) => B, op2: (b: B) => C, op3: (c: C) => D, op4: (d: D) => E, op5: (e: E) => F, op6: (f: F) => G, op7: (g: G) => H, op8: (h: H) => I, op9: (i: I) => J): J;
function pipe(source: unknown, ...ops: ((value: unknown) => unknown)[]): unknown;

Example:

pipe([5, 1, 4], where((n) => n > 1), orderBy((n) => n), toArray()); // => [4, 5]

prepend

Adds an item at the start.

function prepend<T>(item: T): Operator<T, T>;

Example:

pipe([2], prepend(1), toArray()); // => [1, 2]

reverse

Reverses the sequence (materializes when iterated).

function reverse<T>(): Operator<T, T>;

Example:

pipe([1, 2, 3], reverse(), toArray()); // => [3, 2, 1]

select

Maps items with selector(item, index).

function select<T, U>(selector: (item: T, index: number) => U): Operator<T, U>;

Example:

pipe([1, 2], select((n, i) => n * 10 + i), toArray()); // => [10, 21]

selectMany

Maps each item to a sequence and flattens, optionally projecting (item, child).

function selectMany<T, U>(selector: (item: T, index: number) => Iterable<U>): Operator<T, U>;
function selectMany<T, U, R>(selector: (item: T, index: number) => Iterable<U>, resultSelector: (item: T, child: U) => R): Operator<T, R>;

Example:

pipe([[1, 2], [3]], selectMany((a) => a), toArray()); // => [1, 2, 3]

sequenceEqual

True when both sequences have equal items in the same order.

function sequenceEqual<T>(other: Iterable<T>, equals?: EqualityComparer<T>): Terminal<T, boolean>;

Example:

pipe([1, 2], sequenceEqual(new Set([1, 2]))); // => true

single

The only matching item; throws HLP_LINQ_NO_ELEMENTS or HLP_LINQ_MULTIPLE_ELEMENTS.

function single<T>(predicate?: Predicate<T>): Terminal<T, T>;

Example:

pipe([1, 2], single((n) => n > 1)); // => 2

singleOrDefault

The only matching item or defaultValue; still throws HLP_LINQ_MULTIPLE_ELEMENTS for several.

function singleOrDefault<T>(predicate?: Predicate<T>): Terminal<T, T | undefined>;
function singleOrDefault<T, D>(predicate: Predicate<T> | undefined, defaultValue: D): Terminal<T, T | D>;

Example:

pipe([], singleOrDefault()); // => undefined

skip

Skips the first count items.

function skip<T>(count: number): Operator<T, T>;

Example:

pipe([1, 2, 3], skip(1), toArray()); // => [2, 3]

skipLast

Skips the last count items (buffers count items).

function skipLast<T>(count: number): Operator<T, T>;

Example:

pipe([1, 2, 3], skipLast(1), toArray()); // => [1, 2]

skipWhile

Skips leading items while predicate(item, index) holds.

function skipWhile<T>(predicate: (item: T, index: number) => boolean): Operator<T, T>;

Example:

pipe([1, 2, 5, 1], skipWhile((n) => n < 3), toArray()); // => [5, 1]

sum

Compensated sum (0 for empty).

function sum(): Terminal<number, number>;
function sum<T>(selector: (item: T, index: number) => number): Terminal<T, number>;

Example:

pipe([1, 2, 3], sum()); // => 6
const orders = [{ id: 1, total: 120 }, { id: 2, total: 80.5 }];
pipe(orders, sum((o) => o.total)); // => 200.5

take

Takes the first count items and stops pulling from the source.

function take<T>(count: number): Operator<T, T>;

Example:

function* naturals(): Generator<number> {
for (let n = 0; ; n++) yield n;
}
pipe(naturals(), take(3), toArray()); // => [0, 1, 2]

takeLast

Takes the last count items (buffers count items).

function takeLast<T>(count: number): Operator<T, T>;

Example:

pipe([1, 2, 3], takeLast(2), toArray()); // => [2, 3]

takeWhile

Takes leading items while predicate(item, index) holds.

function takeWhile<T>(predicate: (item: T, index: number) => boolean): Operator<T, T>;

Example:

pipe([1, 2, 5, 1], takeWhile((n) => n < 3), toArray()); // => [1, 2]

thenBy

Ascending tie-breaker; only applies to an orderBy result.

function thenBy<T, K>(key: (item: T) => K, compare?: KeyComparer<K>): (source: OrderedSequence<T>) => OrderedSequence<T>;

Example:

const rows = [{ a: 2, b: 1 }, { a: 1, b: 2 }, { a: 1, b: 1 }];
pipe(rows, orderBy((r) => r.a), thenBy((r) => r.b), toArray()); // => [{ a: 1, b: 1 }, { a: 1, b: 2 }, { a: 2, b: 1 }]

thenByDescending

Descending tie-breaker; only applies to an orderBy result.

function thenByDescending<T, K>(key: (item: T) => K, compare?: KeyComparer<K>): (source: OrderedSequence<T>) => OrderedSequence<T>;

Example:

const rows = [{ a: 2, b: 1 }, { a: 1, b: 1 }, { a: 1, b: 2 }];
pipe(rows, orderBy((r) => r.a), thenByDescending((r) => r.b), toArray()); // => [{ a: 1, b: 2 }, { a: 1, b: 1 }, { a: 2, b: 1 }]

toArray

Collects into an array.

function toArray<T>(): Terminal<T, T[]>;

Example:

pipe(new Set([1, 2]), toArray()); // => [1, 2]

toLookup

Collects into a Lookup (key -> values), keys in first-occurrence order.

function toLookup<T, K>(key: (item: T) => K): Terminal<T, Lookup<K, T>>;
function toLookup<T, K, V>(key: (item: T) => K, value: (item: T) => V): Terminal<T, Lookup<K, V>>;

Example:

const users = [{ name: 'Ada', dept: 'HR' }, { name: 'Ben', dept: 'IT' }, { name: 'Cy', dept: 'HR' }];
pipe(users, toLookup((u) => u.dept)).get('HR').length; // => 2

toMap

Collects into a Map; throws HLP_LINQ_DUPLICATE_KEY on a repeated key.

function toMap<T, K>(key: (item: T) => K): Terminal<T, Map<K, T>>;
function toMap<T, K, V>(key: (item: T) => K, value: (item: T) => V): Terminal<T, Map<K, V>>;

Example:

const users = [{ id: 1, name: 'Ada' }, { id: 2, name: 'Ben' }];
const names = pipe(users, toMap((u) => u.id, (u) => u.name));
names.get(2); // => 'Ben'
pipe([...users, { id: 1, name: 'Cy' }], toMap((u) => u.id)); // => throws HLP_LINQ_DUPLICATE_KEY

toSet

Collects into a Set.

function toSet<T>(): Terminal<T, Set<T>>;

Example:

pipe([1, 1, 2], toSet()); // => new Set([1, 2])

union

Distinct items of the source followed by new items of other.

function union<T>(other: Iterable<T>): Operator<T, T>;

Example:

pipe([1, 2], union([2, 3]), toArray()); // => [1, 2, 3]

unionBy

Union by key; the first item per key wins.

function unionBy<T>(other: Iterable<T>, key: (item: T) => unknown): Operator<T, T>;

Example:

const a = [{ id: 1, v: 'a' }, { id: 2, v: 'a' }];
const b = [{ id: 2, v: 'b' }, { id: 3, v: 'b' }];
pipe(a, unionBy(b, (x) => x.id), toArray()); // => [{ id: 1, v: 'a' }, { id: 2, v: 'a' }, { id: 3, v: 'b' }]

where

Keeps items matching predicate(item, index).

function where<T, S extends T>(predicate: (item: T, index: number) => item is S): Operator<T, S>;
function where<T>(predicate: (item: T, index: number) => boolean): Operator<T, T>;

Example:

pipe([1, 2, 3], where((n) => n > 1), toArray()); // => [2, 3]

whereType

Keeps items passing a type guard (the OfType equivalent).

function whereType<T, S extends T>(guard: (item: T) => item is S): Operator<T, S>;

Example:

pipe([1, 'a', 2], whereType((x): x is number => typeof x === 'number'), toArray()); // => [1, 2]

zip

Pairs items with other position-wise, stopping at the shorter sequence.

function zip<T, U>(other: Iterable<U>): Operator<T, [T, U]>;
function zip<T, U, R>(other: Iterable<U>, result: (a: T, b: U) => R): Operator<T, R>;

Example:

pipe([1, 2, 3], zip(['a', 'b']), toArray()); // => [[1, 'a'], [2, 'b']]

Grouping

A group of items sharing key. Re-iterable.

class Grouping<K, T> implements Iterable<T> {
readonly key: K;
readonly items: readonly T[];
[Symbol.iterator](): Iterator<T>;
}

Example:

const words = ['apple', 'avocado', 'banana'];
const groups = pipe(words, groupBy((w) => w.charAt(0)), toArray());
groups.map((g) => [g.key, g.items]); // => [['a', ['apple', 'avocado']], ['b', ['banana']]]

Lookup

Read-only multi-map from toLookup. Missing keys give an empty array.

class Lookup<K, V> implements Iterable<Grouping<K, V>> {
get size(): number;
has(key: K): boolean;
get(key: K): readonly V[];
[Symbol.iterator](): Iterator<Grouping<K, V>>;
}

Example:

const users = [{ name: 'Ada', dept: 'HR' }, { name: 'Ben', dept: 'IT' }, { name: 'Cy', dept: 'HR' }];
const byDept = pipe(users, toLookup((u) => u.dept));
byDept.get('HR').map((u) => u.name); // => ['Ada', 'Cy']
byDept.get('Sales'); // => []

OrderedSequence

A deferred, stable sort. Iterating materializes and sorts the source (keys computed once per item); thenBy/thenByDescending add tie-breakers without re-sorting eagerly.

class OrderedSequence<T> implements Iterable<T> {
thenBy<K>(key: (item: T) => K, compare?: KeyComparer<K>): OrderedSequence<T>;
thenByDescending<K>(key: (item: T) => K, compare?: KeyComparer<K>): OrderedSequence<T>;
[Symbol.iterator](): Iterator<T>;
}

Example:

const people = [{ name: 'Ravi', age: 41 }, { name: 'Ada', age: 36 }, { name: 'Ben', age: 36 }];
[...orderBy((p: { name: string; age: number }) => p.age)(people).thenBy((p) => p.name)].map((p) => p.name); // => ['Ada', 'Ben', 'Ravi']

sameValueZero

SameValueZero, as used by Set, Map, and Array#includes.

const sameValueZero: (a: unknown, b: unknown) => boolean;

toDictionary

Alias of toMap (the .NET ToDictionary name).

const toDictionary: typeof toMap;

Example:

const users = [{ id: 1, name: 'Ada' }, { id: 2, name: 'Ben' }];
pipe(users, toDictionary((u) => u.id)).get(1); // => { id: 1, name: 'Ada' }

EqualityComparer

Equality test used by contains and sequenceEqual.

type EqualityComparer<T> = (a: T, b: T) => boolean;

KeyComparer

Key comparer: negative, zero, or positive.

type KeyComparer<K> = (a: K, b: K) => number;

Operator

Lazy transformation of one sequence into another.

type Operator<T, U> = (source: Iterable<T>) => Iterable<U>;

Terminal

Consumes a sequence into a value.

type Terminal<T, R> = (source: Iterable<T>) => R;