feat: pipeline improvements

This commit is contained in:
2023-11-07 18:00:17 +01:00
parent 8a421cb2fb
commit 51602ffa0f
38 changed files with 183 additions and 130 deletions

View File

@@ -3,19 +3,19 @@
"version": "3.0.0",
"type": "module",
"license": "GPL-3.0-only",
"main": "lib/index.js",
"main": "src/index.js",
"types": "src/types.d.ts",
"files": [
"lib",
"README.md",
"CHANGELOG.md"
],
"scripts": {
"build": "tsup-node --dts",
"docs": "typedoc --json ./docs/docs.json --options ../../typedoc.base.json src/index.ts",
"format": "prettier . -c --ignore-path ../../.gitignore",
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
"lint": "eslint --ext .ts src/",
"lint:fix": "eslint --fix --ext .ts src/",
"lint": "tsc --noEmit && eslint --ext .js src/",
"lint:fix": "eslint --fix --ext .js src/",
"test": "c8 mocha"
},
"devDependencies": {
@@ -30,19 +30,9 @@
"mocha": "10.2.0",
"mocha-junit-reporter": "2.2.0",
"ts-node": "10.9.1",
"tsup": "7.2.0",
"typedoc": "0.24.8",
"typescript": "5.1.6"
},
"tsup": {
"entry": [
"src/index.ts"
],
"sourcemap": true,
"clean": true,
"format": "esm",
"outDir": "lib"
},
"prettier": "@openstapps/prettier-config",
"eslintConfig": {
"extends": [

View File

@@ -15,12 +15,15 @@
/**
* Chunk array into smaller arrays of a specified size.
* @param array The array to chunk.
* @param chunkSize The size of each chunk.
* @template T
* @param array {T[]} The array to chunk.
* @param [chunkSize] {number} The size of each chunk.
* @returns {T[][]}
*/
export function chunk<T>(array: T[], chunkSize = 1): T[][] {
export function chunk(array, chunkSize = 1) {
const arrayCopy = [...array];
const out: T[][] = [];
/** @type {T[][]} */
const out = [];
if (chunkSize <= 0) return out;
while (arrayCopy.length > 0) out.push(arrayCopy.splice(0, chunkSize));
return out;

View File

@@ -15,8 +15,13 @@
/**
* Returns the difference between two arrays.
* @template T
* @param a {T[]}
* @param b {T[]}
* @param transform {(item: T) => unknown}
* @returns {T[]}
*/
export function differenceBy<T>(a: T[], b: T[], transform: (item: T) => unknown) {
export function differenceBy(a, b, transform) {
const disallowed = new Set(b.map(transform));
return a.filter(item => !disallowed.has(transform(item)));

View File

@@ -17,14 +17,20 @@
* Gets a value from a nested object.
* The path must be key names separated by dots.
* If the path doesn't exist, undefined is returned.
*
* @template {unknown} U
* @param object {object}
* @param path {string}
* @returns {U}
*/
export function get<U = unknown>(object: object, path: string): U {
return path.split('.').reduce(
(accumulator, current) =>
accumulator?.hasOwnProperty(current)
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
(accumulator as any)[current]
: undefined,
object,
) as unknown as U;
export function get(object, path) {
return /** @type {U} */ (
path
.split('.')
.reduce(
(accumulator, current) =>
accumulator?.hasOwnProperty(current) ? /** @type {any} */ (accumulator)[current] : undefined,
object,
)
);
}

View File

@@ -15,31 +15,42 @@
/**
* Group an array by a function
* @template T
* @param collection {T[]}
* @param group {(item: T) => string | undefined}
* @returns {Record<string, T[]>}
*/
export function groupBy<T>(collection: T[], group: (item: T) => string | undefined): Record<string, T[]> {
return collection.reduce((accumulator: Record<string, T[]>, item) => {
export function groupBy(collection, group) {
return collection.reduce((accumulator, item) => {
const key = group(item) ?? '';
accumulator[key] = accumulator[key] ?? [];
accumulator[key].push(item);
return accumulator;
}, {});
}, /** @type {Record<string, T[]>} */ ({}));
}
/**
* Group an array by a function (returns a Map, whose keys keep order info of items entry)
* @template T
* @param collection {T[]}
* @param group {(item: T) => string | undefined}
* @returns {Map<string, T[]>}
*/
export function groupByStable<T>(collection: T[], group: (item: T) => string | undefined): Map<string, T[]> {
return collection.reduce((accumulator: Map<string, T[]>, item) => {
export function groupByStable(collection, group) {
return collection.reduce((accumulator, item) => {
const key = group(item) ?? '';
accumulator.set(key, accumulator.get(key) ?? []);
accumulator.get(key)?.push(item);
return accumulator;
}, new Map<string, T[]>());
}, /** @type {Map<string, T[]>} */ new Map());
}
/**
*
* @template {object} T
* @param collection {T[]}
* @param property {keyof T}
* @returns {Record<string, T[]>}
*/
export function groupByProperty<T extends object>(collection: T[], property: keyof T): Record<string, T[]> {
return groupBy(collection, item => item[property] as unknown as string);
export function groupByProperty(collection, property) {
return groupBy(collection, item => item[property]);
}

View File

@@ -14,14 +14,17 @@
*/
/**
* Create an object composed of keys generated from the results of running
* each element of collection thru iteratee. The corresponding value of
* each key is the last element responsible for generating the key. The
* iteratee is invoked with one argument: (value).
* Create an object composed of keys generated from the results of running each element of collection through iteratee.
* The corresponding value of each key is the last element responsible for generating the key.
* The iteratee is invoked with one argument: (value).
* @template T
* @param collection {T[]}
* @param key {(item: T) => string | number}
* @returns {Record<string, T>}
*/
export function keyBy<T>(collection: T[], key: (item: T) => string | number): Record<string, T> {
export function keyBy(collection, key) {
return collection.reduce((accumulator, item) => {
accumulator[key(item)] = item;
return accumulator;
}, {} as Record<string | number, T>);
}, /** @type {Record<string, T>} */ ({}));
}

View File

@@ -19,19 +19,19 @@
* Note: JavaScript is not multithreaded.
* This will not let you run tasks in parallel, use it only to limit how many network requests should be
* running at once.
* @param items the items to iterate through
* @param task the task to be run
* @param limit the maximum number of tasks that should be run asynchronously
* @template T
* @template U
* @param items {T[]} the items to iterate through
* @param task {(item: T, index: number) => Promise<U>} the task to be run
* @param [limit] {number} the maximum number of tasks that should be run asynchronously
* @returns {Promise<U[]>}
*/
export async function mapAsyncLimit<T, U = void>(
items: T[],
task: (item: T, index: number) => Promise<U>,
limit = 5,
): Promise<U[]> {
export async function mapAsyncLimit(items, task, limit = 5) {
return Promise.all(
Array.from({length: limit}).map(async () => {
let i = 0;
const results: U[] = [];
/** @type {U[]} */
const results = [];
for (let item = items.shift(); item !== undefined; item = items.shift()) {
results.push(await task(item, i));
i++;

View File

@@ -15,12 +15,14 @@
/**
* Maps the values of an object to a new object
* @template {object} T
* @template U
* @param object {T}
* @param transform {(value: T[keyof T], key: keyof T) => U}
* @returns {{[key in keyof T]: U}}
*/
export function mapValues<T extends object, U>(
object: T,
transform: (value: T[keyof T], key: keyof T) => U,
): {[key in keyof T]: U} {
const result = {} as {[key in keyof T]: U};
export function mapValues(object, transform) {
const result = /** @type {{[key in keyof T]: U}} */ ({});
for (const key in object) {
if (object.hasOwnProperty(key)) {

View File

@@ -15,9 +15,13 @@
/**
* Returns the minimum value of a collection.
* @template T
* @param array {T[]}
* @param transform {(item: T) => number | undefined}
* @returns {T}
*/
export function minBy<T>(array: T[], transform: (item: T) => number | undefined): T {
export function minBy(array, transform) {
const transforms = array.map(transform);
const min = Math.min(...(transforms.filter(it => !!it) as number[]));
return array.find((_, i) => transforms[i] === min) as T;
const min = Math.min(.../** @type {number[]} */ (transforms.filter(it => !!it)));
return /** @type {T} */ (array.find((_, i) => transforms[i] === min));
}

View File

@@ -15,9 +15,14 @@
/**
* Returns a new object without the specified keys.
* @template {object} T
* @template {keyof T} U
* @param object {T}
* @param keys {U[]}
* @returns {Omit<T, U>}
*/
export function omit<T extends object, U extends keyof T>(object: T, ...keys: U[]): Omit<T, U> {
export function omit(object, ...keys) {
const out = {...object};
for (const key of keys) delete out[key];
return out as Exclude<T, U>;
return out;
}

View File

@@ -16,13 +16,14 @@
/**
* Partitions a list into two lists. One with the elements that satisfy a predicate,
* and one with the elements that don't satisfy the predicate.
* @template T
* @param array {T[]}
* @param transform {(item: T) => boolean}
* @returns {[T[], T[]]}
*/
export function partition<T>(array: T[], transform: (item: T) => boolean): [T[], T[]] {
return array.reduce<[T[], T[]]>(
(accumulator, item) => {
accumulator[transform(item) ? 0 : 1].push(item);
return accumulator;
},
[[], []],
);
export function partition(array, transform) {
return array.reduce((accumulator, item) => {
accumulator[transform(item) ? 0 : 1].push(item);
return accumulator;
}, /** @type {[T[], T[]]} */ ([[], []]));
}

View File

@@ -15,27 +15,34 @@
/**
* Pick a set of properties from an object
* @template {object} T
* @template {keyof T} U
* @param object {T}
* @param keys {U[]}
* @returns {Pick<T, U>}
*/
export function pick<T extends object, U extends keyof T>(object: T, keys: U[]): Pick<T, U> {
export function pick(object, keys) {
return keys.reduce((accumulator, key) => {
if (object.hasOwnProperty(key)) {
accumulator[key] = object[key];
}
return accumulator;
}, {} as Pick<T, U>);
}, /** @type {Pick<T, U>} */ ({}));
}
/**
* Pick a set of properties from an object using a predicate function
* @template {object} T
* @template {keyof T} U
* @param object {T}
* @param predicate {(value: T[U], key: U) => boolean}
* @returns {Pick<T, U>}
*/
export function pickBy<T extends object, U extends keyof T>(
object: T,
predicate: (value: T[U], key: U) => boolean,
): Pick<T, U> {
return (Object.keys(object) as U[]).reduce((accumulator, key) => {
export function pickBy(object, predicate) {
return /** @type {U[]} */ (Object.keys(object)).reduce((accumulator, key) => {
if (predicate(object[key], key)) {
accumulator[key] = object[key];
}
return accumulator;
}, {} as Pick<T, U>);
}, /** @type {Pick<T, U>} */ ({}));
}

View File

@@ -15,8 +15,11 @@
/**
* Shuffles an array
* @template T
* @param array {T[]}
* @returns {T[]}
*/
export function shuffle<T>(array: T[]): T[] {
export function shuffle(array) {
const copy = [...array];
const out = [];

View File

@@ -15,8 +15,11 @@
/**
* sort function for two strings
* @param [a] {string}
* @param [b] {string}
* @returns {number}
*/
export function stringSort(a = '', b = ''): number {
export function stringSort(a = '', b = '') {
if (a < b) return -1;
if (a > b) return 1;
return 0;
@@ -24,9 +27,12 @@ export function stringSort(a = '', b = ''): number {
/**
* sort function for two strings that allows for a custom transform
* @template T
* @param map {(item: T) => string | undefined}
* @returns {(a: T, b: T) => number}
*/
export function stringSortBy<T>(map: (item: T) => string | undefined): (a: T, b: T) => number {
return (a: T, b: T): number => {
export function stringSortBy(map) {
return (a, b) => {
const aValue = map(a) || '';
const bValue = map(b) || '';
if (aValue < bValue) return -1;

View File

@@ -15,17 +15,20 @@
/**
* Sum an an array
* @template {object} T
* @param collection {T[]}
* @param transform {(value: T) => number | undefined}
* @returns {number}
*/
export function sumBy<T extends object>(
collection: T[],
transform: (value: T) => number | undefined,
): number {
export function sumBy(collection, transform) {
return collection.reduce((accumulator, item) => accumulator + (transform(item) || 0), 0);
}
/**
* Sum an array of numbers
* @param collection {Array<number | undefined>}
* @returns {number}
*/
export function sum(collection: Array<number | undefined>): number {
return collection.reduce<number>((accumulator, item) => accumulator + (item || 0), 0);
export function sum(collection) {
return /** @type {number[]} */ (collection).reduce((accumulator, item) => accumulator + (item ?? 0), 0);
}

View File

@@ -13,17 +13,15 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
export type Tree<T> = {
[key: string]: Tree<T>;
} & {
_?: T[] | undefined;
};
/**
*
* @template T
* @param items {T[]}
* @param transform {(item: T) => string[]}
* @returns {import('./types.js').Tree<T>}
*/
export function treeGroupBy<T>(items: T[], transform: (item: T) => string[]): Tree<T> {
const tree: Tree<T> = {};
export function treeGroupBy(items, transform) {
/** @type {import('./types.js').Tree<T>} */
const tree = {};
for (const item of items) {
let currentTree = tree;
@@ -34,7 +32,7 @@ export function treeGroupBy<T>(items: T[], transform: (item: T) => string[]): Tr
}
// eslint-disable-next-line unicorn/no-array-for-each
keys.forEach((key, i) => {
currentTree = currentTree[key] = (currentTree[key] ?? {}) as Tree<T>;
currentTree = currentTree[key] = currentTree[key] ?? {};
if (i === keys.length - 1) {
currentTree._ = currentTree._ ?? [];
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion

View File

@@ -0,0 +1,7 @@
export * from './index.js';
export type Tree<T> = {
[key: string]: Tree<T>;
} & {
_?: T[] | undefined;
};

View File

@@ -15,12 +15,16 @@
/**
* Filter out duplicates from an array.
* @template T
* @param array {T[]}
* @param transform {(item: T) => string | number}
* @returns {T[]}
*/
export function uniqBy<T>(array: T[], transform: (item: T) => string | number): T[] {
export function uniqBy(array, transform) {
return Object.values(
array.reduce((accumulator, current) => {
accumulator[transform(current)] = current;
return accumulator;
}, {} as Record<string | number, T>),
}, /** @type {Record<string, T>} */ ({})),
);
}

View File

@@ -15,7 +15,12 @@
/**
* Zip two arrays together.
* @template T
* @template U
* @param a {T[]}
* @param b {U[]}
* @returns {[T, U][]}
*/
export function zip<T, U>(a: T[], b: U[]): [T, U][] {
export function zip(a, b) {
return a.map((_, i) => [a[i], b[i]]);
}

View File

@@ -12,17 +12,17 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {treeGroupBy} from '../src/index.js';
import {expect} from 'chai';
import {Tree, treeGroupBy} from '../src/index.js';
interface TestItem {
id: number;
path?: string[];
}
/**
* @typedef {{id: number; path?: string[]}} TestItem
* @typedef {import('../src/types.js').Tree<TestItem>} TestItemTree
*/
describe('tree-group', function () {
it('should create a tree', function () {
const items: Array<TestItem> = [
/** @type {TestItem[]} */
const items = [
{
id: 1,
path: ['a', 'b', 'c'],
@@ -35,20 +35,22 @@ describe('tree-group', function () {
const tree = treeGroupBy(items, item => item.path ?? []);
const expectedTree: Tree<TestItem> = {
a: {
/** @type {TestItemTree} */
const expectedTree = {
a: /** @type {TestItemTree} */ ({
b: {
c: {_: [items[0]]},
d: {_: [items[1]]},
} as Tree<TestItem>,
} as Tree<TestItem>,
} as Tree<TestItem>;
},
}),
};
expect(tree).to.deep.equal(expectedTree);
});
it('should also sort empty paths', () => {
const items: Array<TestItem> = [
/** @type {TestItem[]} */
const items = [
{
id: 1,
path: ['a', 'b', 'c'],
@@ -60,14 +62,14 @@ describe('tree-group', function () {
const tree = treeGroupBy(items, item => item.path ?? []);
const expectedTree: Tree<TestItem> = {
const expectedTree = /** @type {TestItemTree} */ ({
a: {
b: {
c: {_: [items[0]]},
} as Tree<TestItem>,
} as Tree<TestItem>,
},
},
_: [items[1]],
} as Tree<TestItem>;
});
expect(tree).to.deep.equal(expectedTree);
});

View File

@@ -74,15 +74,6 @@
"typedoc": "0.24.8",
"typescript": "5.1.6"
},
"tsup": {
"entry": [
"src/index.ts"
],
"sourcemap": true,
"clean": true,
"format": "esm",
"outDir": "lib"
},
"prettier": "@openstapps/prettier-config",
"eslintConfig": {
"extends": [

3
pnpm-lock.yaml generated
View File

@@ -1625,9 +1625,6 @@ importers:
ts-node:
specifier: 10.9.1
version: 10.9.1(@types/node@18.15.3)(typescript@5.1.6)
tsup:
specifier: 7.2.0
version: 7.2.0(ts-node@10.9.1)(typescript@5.1.6)
typedoc:
specifier: 0.24.8
version: 0.24.8(typescript@5.1.6)