mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-08 14:32:50 +00:00
feat: pipeline improvements
This commit is contained in:
@@ -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": [
|
||||
|
||||
@@ -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;
|
||||
@@ -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)));
|
||||
@@ -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,
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -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>} */ ({}));
|
||||
}
|
||||
@@ -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++;
|
||||
@@ -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)) {
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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[]]} */ ([[], []]));
|
||||
}
|
||||
@@ -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>} */ ({}));
|
||||
}
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
7
packages/collection-utils/src/types.d.ts
vendored
Normal file
7
packages/collection-utils/src/types.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export * from './index.js';
|
||||
|
||||
export type Tree<T> = {
|
||||
[key: string]: Tree<T>;
|
||||
} & {
|
||||
_?: T[] | undefined;
|
||||
};
|
||||
@@ -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>} */ ({})),
|
||||
);
|
||||
}
|
||||
@@ -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]]);
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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
3
pnpm-lock.yaml
generated
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user