mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-21 09:03:02 +00:00
feat: pipeline improvements
This commit is contained in:
@@ -3,19 +3,19 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"main": "lib/index.js",
|
"main": "src/index.js",
|
||||||
|
"types": "src/types.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"lib",
|
"lib",
|
||||||
"README.md",
|
"README.md",
|
||||||
"CHANGELOG.md"
|
"CHANGELOG.md"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsup-node --dts",
|
|
||||||
"docs": "typedoc --json ./docs/docs.json --options ../../typedoc.base.json src/index.ts",
|
"docs": "typedoc --json ./docs/docs.json --options ../../typedoc.base.json src/index.ts",
|
||||||
"format": "prettier . -c --ignore-path ../../.gitignore",
|
"format": "prettier . -c --ignore-path ../../.gitignore",
|
||||||
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
|
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
|
||||||
"lint": "eslint --ext .ts src/",
|
"lint": "tsc --noEmit && eslint --ext .js src/",
|
||||||
"lint:fix": "eslint --fix --ext .ts src/",
|
"lint:fix": "eslint --fix --ext .js src/",
|
||||||
"test": "c8 mocha"
|
"test": "c8 mocha"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -30,19 +30,9 @@
|
|||||||
"mocha": "10.2.0",
|
"mocha": "10.2.0",
|
||||||
"mocha-junit-reporter": "2.2.0",
|
"mocha-junit-reporter": "2.2.0",
|
||||||
"ts-node": "10.9.1",
|
"ts-node": "10.9.1",
|
||||||
"tsup": "7.2.0",
|
|
||||||
"typedoc": "0.24.8",
|
"typedoc": "0.24.8",
|
||||||
"typescript": "5.1.6"
|
"typescript": "5.1.6"
|
||||||
},
|
},
|
||||||
"tsup": {
|
|
||||||
"entry": [
|
|
||||||
"src/index.ts"
|
|
||||||
],
|
|
||||||
"sourcemap": true,
|
|
||||||
"clean": true,
|
|
||||||
"format": "esm",
|
|
||||||
"outDir": "lib"
|
|
||||||
},
|
|
||||||
"prettier": "@openstapps/prettier-config",
|
"prettier": "@openstapps/prettier-config",
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|||||||
@@ -15,12 +15,15 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Chunk array into smaller arrays of a specified size.
|
* Chunk array into smaller arrays of a specified size.
|
||||||
* @param array The array to chunk.
|
* @template T
|
||||||
* @param chunkSize The size of each chunk.
|
* @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 arrayCopy = [...array];
|
||||||
const out: T[][] = [];
|
/** @type {T[][]} */
|
||||||
|
const out = [];
|
||||||
if (chunkSize <= 0) return out;
|
if (chunkSize <= 0) return out;
|
||||||
while (arrayCopy.length > 0) out.push(arrayCopy.splice(0, chunkSize));
|
while (arrayCopy.length > 0) out.push(arrayCopy.splice(0, chunkSize));
|
||||||
return out;
|
return out;
|
||||||
@@ -15,8 +15,13 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the difference between two arrays.
|
* 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));
|
const disallowed = new Set(b.map(transform));
|
||||||
|
|
||||||
return a.filter(item => !disallowed.has(transform(item)));
|
return a.filter(item => !disallowed.has(transform(item)));
|
||||||
@@ -17,14 +17,20 @@
|
|||||||
* Gets a value from a nested object.
|
* Gets a value from a nested object.
|
||||||
* The path must be key names separated by dots.
|
* The path must be key names separated by dots.
|
||||||
* If the path doesn't exist, undefined is returned.
|
* 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 {
|
export function get(object, path) {
|
||||||
return path.split('.').reduce(
|
return /** @type {U} */ (
|
||||||
(accumulator, current) =>
|
path
|
||||||
accumulator?.hasOwnProperty(current)
|
.split('.')
|
||||||
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
.reduce(
|
||||||
(accumulator as any)[current]
|
(accumulator, current) =>
|
||||||
: undefined,
|
accumulator?.hasOwnProperty(current) ? /** @type {any} */ (accumulator)[current] : undefined,
|
||||||
object,
|
object,
|
||||||
) as unknown as U;
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -15,31 +15,42 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Group an array by a function
|
* 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[]> {
|
export function groupBy(collection, group) {
|
||||||
return collection.reduce((accumulator: Record<string, T[]>, item) => {
|
return collection.reduce((accumulator, item) => {
|
||||||
const key = group(item) ?? '';
|
const key = group(item) ?? '';
|
||||||
accumulator[key] = accumulator[key] ?? [];
|
accumulator[key] = accumulator[key] ?? [];
|
||||||
accumulator[key].push(item);
|
accumulator[key].push(item);
|
||||||
return accumulator;
|
return accumulator;
|
||||||
}, {});
|
}, /** @type {Record<string, T[]>} */ ({}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Group an array by a function (returns a Map, whose keys keep order info of items entry)
|
* 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[]> {
|
export function groupByStable(collection, group) {
|
||||||
return collection.reduce((accumulator: Map<string, T[]>, item) => {
|
return collection.reduce((accumulator, item) => {
|
||||||
const key = group(item) ?? '';
|
const key = group(item) ?? '';
|
||||||
accumulator.set(key, accumulator.get(key) ?? []);
|
accumulator.set(key, accumulator.get(key) ?? []);
|
||||||
accumulator.get(key)?.push(item);
|
accumulator.get(key)?.push(item);
|
||||||
return accumulator;
|
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[]> {
|
export function groupByProperty(collection, property) {
|
||||||
return groupBy(collection, item => item[property] as unknown as string);
|
return groupBy(collection, item => item[property]);
|
||||||
}
|
}
|
||||||
@@ -14,14 +14,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an object composed of keys generated from the results of running
|
* Create an object composed of keys generated from the results of running each element of collection through iteratee.
|
||||||
* each element of collection thru iteratee. The corresponding value of
|
* The corresponding value of each key is the last element responsible for generating the key.
|
||||||
* each key is the last element responsible for generating the key. The
|
* The iteratee is invoked with one argument: (value).
|
||||||
* 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) => {
|
return collection.reduce((accumulator, item) => {
|
||||||
accumulator[key(item)] = item;
|
accumulator[key(item)] = item;
|
||||||
return accumulator;
|
return accumulator;
|
||||||
}, {} as Record<string | number, T>);
|
}, /** @type {Record<string, T>} */ ({}));
|
||||||
}
|
}
|
||||||
@@ -19,19 +19,19 @@
|
|||||||
* Note: JavaScript is not multithreaded.
|
* 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
|
* This will not let you run tasks in parallel, use it only to limit how many network requests should be
|
||||||
* running at once.
|
* running at once.
|
||||||
* @param items the items to iterate through
|
* @template T
|
||||||
* @param task the task to be run
|
* @template U
|
||||||
* @param limit the maximum number of tasks that should be run asynchronously
|
* @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>(
|
export async function mapAsyncLimit(items, task, limit = 5) {
|
||||||
items: T[],
|
|
||||||
task: (item: T, index: number) => Promise<U>,
|
|
||||||
limit = 5,
|
|
||||||
): Promise<U[]> {
|
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
Array.from({length: limit}).map(async () => {
|
Array.from({length: limit}).map(async () => {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
const results: U[] = [];
|
/** @type {U[]} */
|
||||||
|
const results = [];
|
||||||
for (let item = items.shift(); item !== undefined; item = items.shift()) {
|
for (let item = items.shift(); item !== undefined; item = items.shift()) {
|
||||||
results.push(await task(item, i));
|
results.push(await task(item, i));
|
||||||
i++;
|
i++;
|
||||||
@@ -15,12 +15,14 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps the values of an object to a new object
|
* 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>(
|
export function mapValues(object, transform) {
|
||||||
object: T,
|
const result = /** @type {{[key in keyof T]: U}} */ ({});
|
||||||
transform: (value: T[keyof T], key: keyof T) => U,
|
|
||||||
): {[key in keyof T]: U} {
|
|
||||||
const result = {} as {[key in keyof T]: U};
|
|
||||||
|
|
||||||
for (const key in object) {
|
for (const key in object) {
|
||||||
if (object.hasOwnProperty(key)) {
|
if (object.hasOwnProperty(key)) {
|
||||||
@@ -15,9 +15,13 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the minimum value of a collection.
|
* 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 transforms = array.map(transform);
|
||||||
const min = Math.min(...(transforms.filter(it => !!it) as number[]));
|
const min = Math.min(.../** @type {number[]} */ (transforms.filter(it => !!it)));
|
||||||
return array.find((_, i) => transforms[i] === min) as T;
|
return /** @type {T} */ (array.find((_, i) => transforms[i] === min));
|
||||||
}
|
}
|
||||||
@@ -15,9 +15,14 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new object without the specified keys.
|
* 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};
|
const out = {...object};
|
||||||
for (const key of keys) delete out[key];
|
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,
|
* 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.
|
* 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[]] {
|
export function partition(array, transform) {
|
||||||
return array.reduce<[T[], T[]]>(
|
return array.reduce((accumulator, item) => {
|
||||||
(accumulator, item) => {
|
accumulator[transform(item) ? 0 : 1].push(item);
|
||||||
accumulator[transform(item) ? 0 : 1].push(item);
|
return accumulator;
|
||||||
return accumulator;
|
}, /** @type {[T[], T[]]} */ ([[], []]));
|
||||||
},
|
|
||||||
[[], []],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
@@ -15,27 +15,34 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Pick a set of properties from an object
|
* 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) => {
|
return keys.reduce((accumulator, key) => {
|
||||||
if (object.hasOwnProperty(key)) {
|
if (object.hasOwnProperty(key)) {
|
||||||
accumulator[key] = object[key];
|
accumulator[key] = object[key];
|
||||||
}
|
}
|
||||||
return accumulator;
|
return accumulator;
|
||||||
}, {} as Pick<T, U>);
|
}, /** @type {Pick<T, U>} */ ({}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pick a set of properties from an object using a predicate function
|
* 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>(
|
export function pickBy(object, predicate) {
|
||||||
object: T,
|
return /** @type {U[]} */ (Object.keys(object)).reduce((accumulator, key) => {
|
||||||
predicate: (value: T[U], key: U) => boolean,
|
|
||||||
): Pick<T, U> {
|
|
||||||
return (Object.keys(object) as U[]).reduce((accumulator, key) => {
|
|
||||||
if (predicate(object[key], key)) {
|
if (predicate(object[key], key)) {
|
||||||
accumulator[key] = object[key];
|
accumulator[key] = object[key];
|
||||||
}
|
}
|
||||||
return accumulator;
|
return accumulator;
|
||||||
}, {} as Pick<T, U>);
|
}, /** @type {Pick<T, U>} */ ({}));
|
||||||
}
|
}
|
||||||
@@ -15,8 +15,11 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Shuffles an array
|
* 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 copy = [...array];
|
||||||
const out = [];
|
const out = [];
|
||||||
|
|
||||||
@@ -15,8 +15,11 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* sort function for two strings
|
* 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;
|
||||||
if (a > b) return 1;
|
if (a > b) return 1;
|
||||||
return 0;
|
return 0;
|
||||||
@@ -24,9 +27,12 @@ export function stringSort(a = '', b = ''): number {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* sort function for two strings that allows for a custom transform
|
* 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 {
|
export function stringSortBy(map) {
|
||||||
return (a: T, b: T): number => {
|
return (a, b) => {
|
||||||
const aValue = map(a) || '';
|
const aValue = map(a) || '';
|
||||||
const bValue = map(b) || '';
|
const bValue = map(b) || '';
|
||||||
if (aValue < bValue) return -1;
|
if (aValue < bValue) return -1;
|
||||||
@@ -15,17 +15,20 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sum an an array
|
* Sum an an array
|
||||||
|
* @template {object} T
|
||||||
|
* @param collection {T[]}
|
||||||
|
* @param transform {(value: T) => number | undefined}
|
||||||
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
export function sumBy<T extends object>(
|
export function sumBy(collection, transform) {
|
||||||
collection: T[],
|
|
||||||
transform: (value: T) => number | undefined,
|
|
||||||
): number {
|
|
||||||
return collection.reduce((accumulator, item) => accumulator + (transform(item) || 0), 0);
|
return collection.reduce((accumulator, item) => accumulator + (transform(item) || 0), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sum an array of numbers
|
* Sum an array of numbers
|
||||||
|
* @param collection {Array<number | undefined>}
|
||||||
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
export function sum(collection: Array<number | undefined>): number {
|
export function sum(collection) {
|
||||||
return collection.reduce<number>((accumulator, item) => accumulator + (item || 0), 0);
|
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/>.
|
* 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> {
|
export function treeGroupBy(items, transform) {
|
||||||
const tree: Tree<T> = {};
|
/** @type {import('./types.js').Tree<T>} */
|
||||||
|
const tree = {};
|
||||||
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
let currentTree = tree;
|
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
|
// eslint-disable-next-line unicorn/no-array-for-each
|
||||||
keys.forEach((key, i) => {
|
keys.forEach((key, i) => {
|
||||||
currentTree = currentTree[key] = (currentTree[key] ?? {}) as Tree<T>;
|
currentTree = currentTree[key] = currentTree[key] ?? {};
|
||||||
if (i === keys.length - 1) {
|
if (i === keys.length - 1) {
|
||||||
currentTree._ = currentTree._ ?? [];
|
currentTree._ = currentTree._ ?? [];
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// 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.
|
* 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(
|
return Object.values(
|
||||||
array.reduce((accumulator, current) => {
|
array.reduce((accumulator, current) => {
|
||||||
accumulator[transform(current)] = current;
|
accumulator[transform(current)] = current;
|
||||||
return accumulator;
|
return accumulator;
|
||||||
}, {} as Record<string | number, T>),
|
}, /** @type {Record<string, T>} */ ({})),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,12 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Zip two arrays together.
|
* 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]]);
|
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
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
import {treeGroupBy} from '../src/index.js';
|
||||||
import {expect} from 'chai';
|
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 () {
|
describe('tree-group', function () {
|
||||||
it('should create a tree', function () {
|
it('should create a tree', function () {
|
||||||
const items: Array<TestItem> = [
|
/** @type {TestItem[]} */
|
||||||
|
const items = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
path: ['a', 'b', 'c'],
|
path: ['a', 'b', 'c'],
|
||||||
@@ -35,20 +35,22 @@ describe('tree-group', function () {
|
|||||||
|
|
||||||
const tree = treeGroupBy(items, item => item.path ?? []);
|
const tree = treeGroupBy(items, item => item.path ?? []);
|
||||||
|
|
||||||
const expectedTree: Tree<TestItem> = {
|
/** @type {TestItemTree} */
|
||||||
a: {
|
const expectedTree = {
|
||||||
|
a: /** @type {TestItemTree} */ ({
|
||||||
b: {
|
b: {
|
||||||
c: {_: [items[0]]},
|
c: {_: [items[0]]},
|
||||||
d: {_: [items[1]]},
|
d: {_: [items[1]]},
|
||||||
} as Tree<TestItem>,
|
},
|
||||||
} as Tree<TestItem>,
|
}),
|
||||||
} as Tree<TestItem>;
|
};
|
||||||
|
|
||||||
expect(tree).to.deep.equal(expectedTree);
|
expect(tree).to.deep.equal(expectedTree);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should also sort empty paths', () => {
|
it('should also sort empty paths', () => {
|
||||||
const items: Array<TestItem> = [
|
/** @type {TestItem[]} */
|
||||||
|
const items = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
path: ['a', 'b', 'c'],
|
path: ['a', 'b', 'c'],
|
||||||
@@ -60,14 +62,14 @@ describe('tree-group', function () {
|
|||||||
|
|
||||||
const tree = treeGroupBy(items, item => item.path ?? []);
|
const tree = treeGroupBy(items, item => item.path ?? []);
|
||||||
|
|
||||||
const expectedTree: Tree<TestItem> = {
|
const expectedTree = /** @type {TestItemTree} */ ({
|
||||||
a: {
|
a: {
|
||||||
b: {
|
b: {
|
||||||
c: {_: [items[0]]},
|
c: {_: [items[0]]},
|
||||||
} as Tree<TestItem>,
|
},
|
||||||
} as Tree<TestItem>,
|
},
|
||||||
_: [items[1]],
|
_: [items[1]],
|
||||||
} as Tree<TestItem>;
|
});
|
||||||
|
|
||||||
expect(tree).to.deep.equal(expectedTree);
|
expect(tree).to.deep.equal(expectedTree);
|
||||||
});
|
});
|
||||||
@@ -74,15 +74,6 @@
|
|||||||
"typedoc": "0.24.8",
|
"typedoc": "0.24.8",
|
||||||
"typescript": "5.1.6"
|
"typescript": "5.1.6"
|
||||||
},
|
},
|
||||||
"tsup": {
|
|
||||||
"entry": [
|
|
||||||
"src/index.ts"
|
|
||||||
],
|
|
||||||
"sourcemap": true,
|
|
||||||
"clean": true,
|
|
||||||
"format": "esm",
|
|
||||||
"outDir": "lib"
|
|
||||||
},
|
|
||||||
"prettier": "@openstapps/prettier-config",
|
"prettier": "@openstapps/prettier-config",
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|||||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -1625,9 +1625,6 @@ importers:
|
|||||||
ts-node:
|
ts-node:
|
||||||
specifier: 10.9.1
|
specifier: 10.9.1
|
||||||
version: 10.9.1(@types/node@18.15.3)(typescript@5.1.6)
|
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:
|
typedoc:
|
||||||
specifier: 0.24.8
|
specifier: 0.24.8
|
||||||
version: 0.24.8(typescript@5.1.6)
|
version: 0.24.8(typescript@5.1.6)
|
||||||
|
|||||||
Reference in New Issue
Block a user