@romatech/linq
v1.0.1
Published
A lightweight, powerful library that extends JavaScript arrays with LINQ-style methods for querying, transforming, and aggregating data. Supports filtering, projection, joining, grouping, sorting, set operations, and more — all with a fluent and expressiv
Maintainers
Readme
@romatech/linq
🧠 A comprehensive utility that extends Array.prototype with a rich set of methods for:
✅ Projection & Transformation: select, selectMany, cast, ofType
✅ Access & Query: where, first, firstOrDefault, last, lastOrDefault, single, singleOrDefault, any, all, contains, elementAt, elementAtOrDefault
✅ Aggregation: count, sum, average, min, max, aggregate
✅ Sorting & Flow: orderBy, orderByDesc, thenBy, thenByDesc, take, skip, takeWhile, skipWhile
✅ Set & Collection: distinct, distinctBy, union, intersect, except
✅ Joins & Grouping: join, groupJoin, groupBy
✅ Conversion & Utilities: toDictionary, append, prepend, defaultIfEmpty, sequenceEqual, zip
Chainable, immutable, fully typed, and compatible with CommonJS and ES Modules.
🧩 This package adds safe, non-enumerable methods to Array.prototype.
🚀 Installation
npm install @romatech/linq📦 Usage
✅ You must import the package once to enable prototype extensions.
CommonJS
require('@romatech/linq');
const numbers = [1, 2, 3, 4, 5];
const result = numbers
.where(x => x > 2)
.orderByDesc(x => x)
.take(2);
console.log(result); // [5, 4]ES Modules
import '@romatech/linq';
const users = [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
{ name: 'Charlie', age: 30 },
];
const grouped = users.groupBy(u => u.age);
console.log(grouped);
// {
// 25: [{ name: 'Bob', age: 25 }],
// 30: [{ name: 'Alice', age: 30 }, { name: 'Charlie', age: 30 }]
// }🧠 API
Projection & Transformation
carray.select(fn) — Maps each element by the selector function.
array.selectMany(fn) — Maps each element and flattens the result.
array.cast() — Casts elements (identity).
array.ofType(type) — Filters elements by JavaScript type.
Access & Query
array.where(predicate) — Filters elements by predicate.
array.first(predicate?) — Returns the first element matching predicate or first element.
array.firstOrDefault(predicate?) — Like first but returns null if none.
array.last(predicate?) — Returns the last element matching predicate or last element.
array.lastOrDefault(predicate?) — Like last but returns null if none.
array.single(predicate?) — Returns exactly one element matching predicate; throws if zero or multiple.
array.singleOrDefault(predicate?) — Returns one or zero elements matching predicate; throws if multiple.
array.any(predicate?) — Checks if any element matches predicate or if array has elements.
array.all(predicate) — Checks if all elements satisfy predicate.
array.contains(value) — Checks if value exists in array.
array.elementAt(index) — Returns element at index; throws if out of bounds.
array.elementAtOrDefault(index) — Returns element or null if out of bounds.
Aggregation
array.count(predicate?) — Counts elements matching predicate or total.
array.sum(selector?) — Sums values or selected values.
array.average(selector?) — Computes average of values or selected values.
array.min(selector?) — Returns minimum value or selected values.
array.max(selector?) — Returns maximum value or selected values.
array.aggregate(accumulator, seed) — Aggregates values using accumulator function and seed.
Sorting & Flow
array.orderBy(fn) — Sort ascending by selector.
array.orderByDesc(fn) — Sort descending by selector.
array.thenBy(fn) — Chain secondary ascending sort.
array.thenByDesc(fn) — Chain secondary descending sort.
array.take(n) — Takes first n elements.
array.skip(n) — Skips first n elements.
array.takeWhile(predicate) — Takes elements while predicate is true.
array.skipWhile(predicate) — Skips elements while predicate is true.
Set & Collection
array.distinct() — Removes deep-equal duplicates.
array.distinctBy(fn) — Removes duplicates by selector return value.
array.union(otherArray) — Union of arrays (unique).
array.intersect(otherArray) — Intersection of arrays.
array.except(otherArray) — Elements except those in otherArray.
Joins & Grouping
array.join(inner, outerKeySelector, innerKeySelector, resultSelector) — Joins arrays on keys.
array.groupJoin(inner, outerKeySelector, innerKeySelector, resultSelector) — Groups join results.
array.groupBy(keySelector) — Groups elements by key.
Conversion & Utilities
array.toDictionary(keySelector, valueSelector?) — Converts array to dictionary/object.
array.append(item) — Returns new array with item appended.
array.prepend(item) — Returns new array with item prepended.
array.defaultIfEmpty(defaultValue) — Returns default if array empty.
array.sequenceEqual(other) — Checks if arrays are deeply equal.
array.zip(other, resultSelector?) — Combines arrays element-wise.
🧱 TypeScript Support
Fully typed with comprehensive definitions available.
type Selector<T> = (item: T) => any;
interface OrderedArray<T> extends Array<T> {
thenBy(fn: Selector<T>): OrderedArray<T>;
thenByDesc(fn: Selector<T>): OrderedArray<T>;
}
interface Array<T> {
select<U>(fn: (item: T) => U): U[];
selectMany<U>(fn: (item: T) => U[]): U[];
cast(): T[];
ofType(type: string): T[];
where(fn: (item: T) => boolean): T[];
first(fn?: (item: T) => boolean): T | undefined;
firstOrDefault(fn?: (item: T) => boolean): T | null;
last(fn?: (item: T) => boolean): T | undefined;
lastOrDefault(fn?: (item: T) => boolean): T | null;
single(fn?: (item: T) => boolean): T;
singleOrDefault(fn?: (item: T) => boolean): T | null;
any(fn?: (item: T) => boolean): boolean;
all(fn: (item: T) => boolean): boolean;
contains(value: T): boolean;
elementAt(index: number): T;
elementAtOrDefault(index: number): T | null;
count(fn?: (item: T) => boolean): number;
sum(fn?: (item: T) => number): number;
average(fn?: (item: T) => number): number;
min(fn?: (item: T) => number): number;
max(fn?: (item: T) => number): number;
aggregate<U>(fn: (acc: U, val: T) => U, seed: U): U;
orderBy(fn: (item: T) => any): OrderedArray<T>;
orderByDesc(fn: (item: T) => any): OrderedArray<T>;
thenBy(fn: (item: T) => any): OrderedArray<T>;
thenByDesc(fn: (item: T) => any): OrderedArray<T>;
take(n: number): T[];
skip(n: number): T[];
takeWhile(fn: (item: T) => boolean): T[];
skipWhile(fn: (item: T) => boolean): T[];
distinct(): T[];
distinctBy(fn: (item: T) => any): T[];
union(other: T[]): T[];
intersect(other: T[]): T[];
except(other: T[]): T[];
join<U, R>(
inner: U[],
outerKeySelector: (outer: T) => any,
innerKeySelector: (inner: U) => any,
resultSelector: (outer: T, inner: U) => R
): R[];
groupJoin<U, R>(
inner: U[],
outerKeySelector: (outer: T) => any,
innerKeySelector: (inner: U) => any,
resultSelector: (outer: T, inners: U[]) => R
): R[];
groupBy<K extends keyof any>(fn: (item: T) => K): Record<K, T[]>;
toDictionary<K extends keyof any, V>(keySelector: (item: T) => K, valueSelector?: (item: T) => V): Record<K, V>;
append(item: T): T[];
prepend(item: T): T[];
defaultIfEmpty(item: T): T[];
sequenceEqual(other: T[]): boolean;
zip<U, R>(other: U[], resultSelector?: (a: T, b: U) => R): R[];
}✅ Compatibility
Node.js LTS (≥ v11 recommended; Node 18+ fully supported)
Works in browsers (via bundlers like Vite/Webpack)
Fully compatible with CommonJS and ESM
🧪 Tests
npm install
npm testAll tests are implemented with Jest and validate sorting logic and method chaining behavior.
⚠️ Prototype Extension Warning
This package extends Array.prototype with non-enumerable methods for rich querying and transformation.
✅ Safe in most applications. ❌ Avoid in libraries where global prototype changes are discouraged.
🪪 License
MIT © RomaTech / Leandro Romanelli
