ts-iterable-functions
v5.1.0
Published
A collection of type-safe functions for operating over iterable sequences, with specialized versions that generate unary functions for use in pipes. Will feel immediately familiar for users of MS LINQ-to-objects.
Downloads
174
Readme
ts-iterable-functions
A collection of type-safe functions for operating over iterable sequences, with specialized versions that generate unary functions for use in pipes. Will feel immediately familiar for users of MS LINQ-to-objects.
Note
Versions > 5.x are now built with esbuild and no longer support IE11.
Installation
npm install ts-iterable-functions ts-functional-pipe \
ts-equality-comparer ts-comparer-builder
Usage
First, import pipeInto
from ts-functional-pipe
:
import { pipeInto as pp } from "ts-functional-pipe";
Let's make a collection of cars
const cars = [
{
manufacturer: "Ford",
model: "Escort",
},
{
manufacturer: "Ford",
model: "Cortina",
},
{
manufacturer: "Renault",
model: "Clio",
},
{
manufacturer: "Vauxhall",
model: "Corsa",
},
{
manufacturer: "Ford",
model: "Fiesta",
},
{
manufacturer: "Fiat",
model: "500",
},
];
...and sort them by manufacturer, and then by model:
const orderedCars = pp(
cars,
orderBy((c) => c.manufacturer),
thenBy((c) => c.model),
toArray()
);
Or we could count the number of cars for each manufacturer:
const carsPerManufacturer = pp(
cars,
groupBy((c) => c.manufacturer),
map((g) => ({
count: _count(g),
manufacturer: g.key,
})),
orderByDescending((c) => c.count),
thenBy((c) => c.manufacturer)
);
for (var c of carsPerManufacturer) {
console.log(`${c.manufacturer} : ${c.count}`);
}
to give
Ford : 3
Fiat : 1
Renault : 1
Vauxhall : 1
Functions for iterable sequences
Almost every function in this collection is designed to work over Iterable<T>
.
An example
Let's start with map
(which is also aliased to select
) to see how it works.
Here's an Iterable<number>
const src = [1, 2, 3];
We can use the _map
function to transform this as follows:
const times2 = _map(src, (x) => x + x);
Two forms of the same function
All of the functions that transform iterables in this library exist in two forms.
The first form is the one we used above and looks like this:
function _someOperator<T, A, B, R>(src: T, a: A, b: B): R;
and by convention is prefixed with an _underscore
. While handy in their own way, composing these functions is ugly.
//this looks awful
const times2squared = _map(
_map(src, (x) => x + x),
(x) => x * x
);
The pipeable function
If, instead, we had functions that look like this
function someOperator<T, A, B, R>(a: A, b: B): (src: T) => R;
where the function returns a unary function of the form (src: T) => R
, we can use them in pipes (where the output of one function is fed in to the input of the next function).
In fact, we can transform _someOperator
into someOperator
(preserving all type information) with the deferP0
function (from ts-functional-pipe):
const deferP0 =
<P0, A extends any[], R>(fn: (src: P0, ...args: A) => R) =>
(...args: A) =>
(src: P0): R =>
fn(src, ...args);
so, we could take the _map
function above and transform it into the pipeable form with a simple call to deferP0(_map)
.
All functions that transform Iterable<T>
in the library exist in the two forms. So, for instance the map function exists as _map
and map
. Moving forward, we'll be avoiding the _underscored
functions.
Usage with pipes
The functions in this library are designed to be composed. Package ts-functional-pipe
offers excellent type-inference for this purpose. There is good information about to use the pipe
/pipeInto
/compose
functions it contains in the README
over there.
Making times2squared
readable
Let's use pipeInto
(imported above as pp
) to pipe our iterable into a chain of unary functions, generated (in this case) using the map
function discussed above:
const src = [1, 2, 3];
const times2squared = pp(
src,
map((x) => x + x),
map((x) => x * x)
);
Types in the pipe are preserved
Due to some funky type-definitions in ts-functional-pipe, types flow through the pipe nicely:
const src = [1, 2, 3];
const toStringRepeated = pp(
src,
map((x) => x.toString()), // here x is number
map((s) => s + s) // here s is string
); // returns a string
and all types are correctly inferred.
More coming soon.
Generators
range
, repeat
, repeatGenerate
Transformers
aggregate
, all
/every
, append
, average
, concat
, count
, defaultIfEmpty
, distinctBy
, distinct
, elementAt
, except
, firstOrDefault
, first
, flatten
, forEach
, fullOuterGroupJoin
, fullOuterJoin
, groupAdjacent
, groupBy
, groupJoin
, intersect
, isSubsetOf
, isSupersetOf
, join
, lastOrDefault
, last
, leftOuterJoin
, maxBy
, max
, minBy
, min
, orderByDescending
, orderBy
, preprend
, reduce
, reduceRight
, reverse
, selectMany
/flapMap
, select
/map
, sequenceEqual
, singleOrDefault
, single
, skip
, skipWhile
, some
, sum
, take
, takeWhile
, thenByDescending
, thenBy
, toArray
, toLookup
, toMap
, toSet
, union
, where
/filter
, zipAll
, zip
, zipMap
acknowledgements
Created using the wonderful https://github.com/gjuchault/typescript-library-starter.
Typescript Library Starter
Yet another (opinionated) typescript library starter template.
Opinions and limitations
- Relies as much as possible on each included library's defaults
- Only rely on GitHub Actions
- Do not include documentation generation
Getting started
npx degit gjuchault/typescript-library-starter my-project
or click onUse this template
button on GitHub!cd my-project
npm install
git init
(if you used degit)npm run setup
To enable deployment, you will need to:
- Setup
NPM_TOKEN
secret in GitHub actions (Settings > Secrets > Actions) - Give
GITHUB_TOKEN
write permissions for GitHub releases (Settings > Actions > General > Workflow permissions)
Features
Node.js, npm version
Typescript Library Starter relies on volta to ensure node version to be consistent across developers. It's also used in the GitHub workflow file.
Typescript
Leverages esbuild for blazing fast builds, but keeps tsc
to generate .d.ts
files.
Generates two builds to support both ESM and CJS.
Commands:
build
: runs typechecking then generates CJS, ESM andd.ts
files in thebuild/
directoryclean
: removes thebuild/
directorytype:dts
: only generatesd.ts
type:check
: only run typecheckingtype:build
: only generates CJS and ESM
Tests
typescript-library-starter uses vitest. The coverage is done through vitest, using c8.
Commands:
test
: runs vitest test runnertest:watch
: runs vitest test runner in watch modetest:coverage
: runs vitest test runner and generates coverage reports
Format & lint
This template relies on the combination of eslint — through typescript-eslint for linting and prettier for formatting. It also uses cspell to ensure spelling
Commands:
format
: runs prettier with automatic fixingformat:check
: runs prettier without automatic fixing (used in CI)lint
: runs eslint with automatic fixinglint:check
: runs eslint without automatic fixing (used in CI)spell:check
: runs spellchecking
Releasing
Under the hood, this library uses semantic-release and commitizen.
The goal is to avoid manual release process. Using semantic-release
will automatically create a github release (hence tags) as well as an npm release.
Based on your commit history, semantic-release
will automatically create a patch, feature or breaking release.
Commands:
cz
: interactive CLI that helps you generate a proper git commit message, using commitizensemantic-release
: triggers a release (used in CI)