diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..12bcac415 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @raon0211 diff --git a/.github/workflows/broken-link-checker.yml b/.github/workflows/broken-link-checker.yml index 928a2da2d..2a89ad90b 100644 --- a/.github/workflows/broken-link-checker.yml +++ b/.github/workflows/broken-link-checker.yml @@ -18,4 +18,4 @@ jobs: cache-dependency-path: 'yarn.lock' node-version-file: '.nvmrc' - run: yarn install - - run: yarn blc ${{ github.event.inputs.url }} --ro + - run: yarn blc ${{ github.event.inputs.url || 'https://es-toolkit.slash.page' }} --ro diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml new file mode 100644 index 000000000..979385e0b --- /dev/null +++ b/.github/workflows/codspeed.yml @@ -0,0 +1,25 @@ +name: codspeed-benchmarks + +on: + push: + branches: + - 'main' + pull_request: + +jobs: + benchmarks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: corepack enable + - uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: 'yarn' + name: Install dependencies + - run: yarn install + - name: Run benchmarks + uses: CodSpeedHQ/action@v2 + with: + run: yarn vitest bench + token: ${{ secrets.CODSPEED_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index bf972cc7a..d013a8974 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,45 @@ # es-toolkit Changelog +## Version v1.4.0 + +Released on June 15th, 2024. + +### Features + +- Add support for [random](https://es-toolkit.slash.page/reference/math/random.html). (https://github.com/toss/es-toolkit/pull/53) +- Add support for [randomInt](https://es-toolkit.slash.page/reference/math/randomInt.html). ([99a34e4](https://github.com/toss/es-toolkit/commit/99a34e4e9944c1b843e9d97dff0b5ff4e5eec260)) +- Add support for using AbortSignals to cancel the `Promise` returned by `delay`. (https://github.com/toss/es-toolkit/pull/52) + +### Performance Optimizations + +- Optimized `uniqBy`. ([60e7974](https://github.com/toss/es-toolkit/commit/60e79741271e645bfa551f708466e43b136f69b1)) + +## Version v1.3.1 + +Released on June 15th, 2024. + +- Fixed a bug in `dropWhile` where it incorrectly returned the entire array when no elements matched the predicate. (https://github.com/toss/es-toolkit/pull/49) + +## Version v1.3.0 + +Released on June 14th, 2024. + +### Features + +- Add support for using AbortSignals to cancel `debounce`d functions. (https://github.com/toss/es-toolkit/pull/45) + +### Performance Optimizations + +- Optimize the time complexity of `intersection`. (https://github.com/toss/es-toolkit/pull/47) + +## Version v1.2.2 + +Released on June 13th, 2024. + +- Add support for `readonly` arrays in array utilities. (https://github.com/toss/es-toolkit/pull/32, [e595e5e](https://github.com/toss/es-toolkit/commit/e595e5e017e1f2cb138b1ad3d708635efc5e289e)) +- Optimize the time complexity of `uniq`. (https://github.com/toss/es-toolkit/pull/40) +- Optimize the time complexity of `intersectionBy`. (https://github.com/toss/es-toolkit/pull/44) + ## Version v1.2.1 Released on June 13th, 2024. diff --git a/README-ko_kr.md b/README-ko_kr.md index 9b6b2e6ea..b34babed2 100644 --- a/README-ko_kr.md +++ b/README-ko_kr.md @@ -1,6 +1,6 @@ ![](./docs/public/og.png) -# es-toolkit · [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/toss/slash/blob/main/LICENSE) +# es-toolkit · [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/toss/slash/blob/main/LICENSE) [![codecov](https://codecov.io/gh/toss/es-toolkit/graph/badge.svg?token=8N5S3AR3C7)](https://codecov.io/gh/toss/es-toolkit) [English](https://github.com/toss/es-toolkit/blob/main/README.md) | 한국어 diff --git a/README.md b/README.md index 73c54b6d2..8ae933e0b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![](./docs/public/og.png) -# es-toolkit · [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/toss/slash/blob/main/LICENSE) +# es-toolkit · [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/toss/slash/blob/main/LICENSE) [![codecov](https://codecov.io/gh/toss/es-toolkit/graph/badge.svg?token=8N5S3AR3C7)](https://codecov.io/gh/toss/es-toolkit) English | [한국어](https://github.com/toss/es-toolkit/blob/main/README-ko_kr.md) diff --git a/benchmarks/chunk.bench.ts b/benchmarks/chunk.bench.ts index 3d658c363..4acd3d4dc 100644 --- a/benchmarks/chunk.bench.ts +++ b/benchmarks/chunk.bench.ts @@ -3,11 +3,11 @@ import { chunk as chunkToolkit } from 'es-toolkit'; import { chunk as chunkLodash } from 'lodash'; describe('chunk', () => { - bench('es-toolkit', () => { + bench('es-toolkit/chunk', () => { chunkToolkit([1, 2, 3, 4, 5, 6], 3); }); - bench('lodash', () => { + bench('lodash/chunk', () => { chunkLodash([1, 2, 3, 4, 5, 6], 3); }); }); diff --git a/benchmarks/clamp.bench.ts b/benchmarks/clamp.bench.ts index b9e1fd73a..4ff8d4336 100644 --- a/benchmarks/clamp.bench.ts +++ b/benchmarks/clamp.bench.ts @@ -3,12 +3,12 @@ import { clamp as clampToolkit } from 'es-toolkit'; import { clamp as clampLodash } from 'lodash'; describe('clamp', () => { - bench('es-toolkit', () => { + bench('es-toolkit/clamp', () => { clampToolkit(10, 5, 15); clampToolkit(10, 5); }); - bench('lodash', () => { + bench('lodash/clamp', () => { clampLodash(10, 5, 15); clampLodash(10, 5); }); diff --git a/benchmarks/difference.bench.ts b/benchmarks/difference.bench.ts index 4caedc841..474e130b3 100644 --- a/benchmarks/difference.bench.ts +++ b/benchmarks/difference.bench.ts @@ -3,11 +3,11 @@ import { difference as differenceToolkit } from 'es-toolkit'; import { difference as differenceLodash } from 'lodash'; describe('difference', () => { - bench('es-toolkit', () => { + bench('es-toolkit/difference', () => { differenceToolkit([1, 2, 3], [2]); }); - bench('lodash', () => { + bench('lodash/difference', () => { differenceLodash([1, 2, 3], [2]); }); }); diff --git a/benchmarks/differenceBy.bench.ts b/benchmarks/differenceBy.bench.ts index f668c1f46..59a64c18a 100644 --- a/benchmarks/differenceBy.bench.ts +++ b/benchmarks/differenceBy.bench.ts @@ -3,11 +3,11 @@ import { differenceBy as differenceByToolkit } from 'es-toolkit'; import { differenceBy as differenceByLodash } from 'lodash'; describe('differenceBy', () => { - bench('es-toolkit', () => { + bench('es-toolkit/differenceBy', () => { differenceByToolkit([1.2, 2.3, 3.4], [1.2], Math.floor); }); - bench('lodash', () => { + bench('lodash/differenceBy', () => { differenceByLodash([1.2, 2.3, 3.4], [1.2], Math.floor); }); }); diff --git a/benchmarks/differenceWith.bench.ts b/benchmarks/differenceWith.bench.ts index 0de4609f8..876677ef4 100644 --- a/benchmarks/differenceWith.bench.ts +++ b/benchmarks/differenceWith.bench.ts @@ -3,11 +3,11 @@ import { differenceWith as differenceWithToolkit } from 'es-toolkit'; import { differenceWith as differenceWithLodash } from 'lodash'; describe('differenceWith', () => { - bench('es-toolkit', () => { + bench('es-toolkit/differenceWith', () => { differenceWithToolkit([1.2, 2.3, 3.4], [1.2], (x, y) => Math.floor(x) === Math.floor(y)); }); - bench('lodash', () => { + bench('lodash/differenceWith', () => { differenceWithLodash([1.2, 2.3, 3.4], [1.2], (x, y) => Math.floor(x) === Math.floor(y)); }); }); diff --git a/benchmarks/drop.bench.ts b/benchmarks/drop.bench.ts index 761638302..ee1cf75b7 100644 --- a/benchmarks/drop.bench.ts +++ b/benchmarks/drop.bench.ts @@ -3,11 +3,11 @@ import { drop as dropToolkit } from 'es-toolkit'; import { drop as dropLodash } from 'lodash'; describe('drop', () => { - bench('es-toolkit', () => { + bench('es-toolkit/drop', () => { dropToolkit([1, 2, 3, 4, 5, 6], 3); }); - bench('lodash', () => { + bench('lodash/drop', () => { dropLodash([1, 2, 3, 4, 5, 6], 3); }); }); diff --git a/benchmarks/dropRight.bench.ts b/benchmarks/dropRight.bench.ts index 3375c3726..6bccf2abe 100644 --- a/benchmarks/dropRight.bench.ts +++ b/benchmarks/dropRight.bench.ts @@ -3,11 +3,11 @@ import { dropRight as dropRightToolkit } from 'es-toolkit'; import { dropRight as dropRightLodash } from 'lodash'; describe('dropRight', () => { - bench('es-toolkit', () => { + bench('es-toolkit/dropRight', () => { dropRightToolkit([1, 2, 3, 4, 5, 6], 3); }); - bench('lodash', () => { + bench('lodash/dropRight', () => { dropRightLodash([1, 2, 3, 4, 5, 6], 3); }); }); diff --git a/benchmarks/dropRightWhile.bench.ts b/benchmarks/dropRightWhile.bench.ts index 3ea111a15..94f6b7539 100644 --- a/benchmarks/dropRightWhile.bench.ts +++ b/benchmarks/dropRightWhile.bench.ts @@ -3,11 +3,11 @@ import { dropRightWhile as dropRightWhileToolkit } from 'es-toolkit'; import { dropRightWhile as dropRightWhileLodash } from 'lodash'; describe('dropRightWhile', () => { - bench('es-toolkit', () => { + bench('es-toolkit/dropRightWhile', () => { dropRightWhileToolkit([1.2, 2.3, 3.4], x => x < 2); }); - bench('lodash', () => { + bench('lodash/dropRightWhile', () => { dropRightWhileLodash([1.2, 2.3, 3.4], x => x < 2); }); }); diff --git a/benchmarks/dropWhile.bench.ts b/benchmarks/dropWhile.bench.ts index b3eebc54c..3df02f019 100644 --- a/benchmarks/dropWhile.bench.ts +++ b/benchmarks/dropWhile.bench.ts @@ -3,11 +3,11 @@ import { dropWhile as dropWhileToolkit } from 'es-toolkit'; import { dropWhile as dropWhileLodash } from 'lodash'; describe('dropWhile', () => { - bench('es-toolkit', () => { + bench('es-toolkit/dropWhile', () => { dropWhileToolkit([1.2, 2.3, 3.4], x => x < 2); }); - bench('lodash', () => { + bench('lodash/dropWhile', () => { dropWhileLodash([1.2, 2.3, 3.4], x => x < 2); }); }); diff --git a/benchmarks/groupBy.bench.ts b/benchmarks/groupBy.bench.ts index 04349d904..21759dda0 100644 --- a/benchmarks/groupBy.bench.ts +++ b/benchmarks/groupBy.bench.ts @@ -3,7 +3,7 @@ import { groupBy as groupByToolkit } from 'es-toolkit'; import { groupBy as groupByLodash } from 'lodash'; describe('groupBy', () => { - bench('es-toolkit', () => { + bench('es-toolkit/groupBy', () => { const array = [ { category: 'fruit', name: 'apple' }, { category: 'fruit', name: 'banana' }, @@ -15,7 +15,7 @@ describe('groupBy', () => { groupByToolkit(array, item => item.category); }); - bench('lodash', () => { + bench('lodash/groupBy', () => { const array = [ { category: 'fruit', name: 'apple' }, { category: 'fruit', name: 'banana' }, diff --git a/benchmarks/intersection.bench.ts b/benchmarks/intersection.bench.ts index eda95d79a..032087181 100644 --- a/benchmarks/intersection.bench.ts +++ b/benchmarks/intersection.bench.ts @@ -2,12 +2,28 @@ import { bench, describe } from 'vitest'; import { intersection as intersectionToolkit } from 'es-toolkit'; import { intersection as intersectionLodash } from 'lodash'; -describe('intersection', () => { - bench('es-toolkit', () => { - intersectionToolkit([1, 2, 3], [2, 4]); +describe('intersection, small arrays', () => { + const array1 = [1, 2, 3]; + const array2 = [2, 4]; + + bench('es-toolkit/intersection', () => { + intersectionToolkit(array1, array2); + }); + + bench('lodash/intersection', () => { + intersectionLodash(array1, array2); + }); +}); + +describe('intersection, large arrays', () => { + const array1 = Array.from({ length: 10000 }, () => Math.floor(Math.random() * 1000)); + const array2 = Array.from({ length: 10000 }, () => Math.floor(Math.random() * 1000)); + + bench('es-toolkit/intersection', () => { + intersectionToolkit(array1, array2); }); - bench('lodash', () => { - intersectionLodash([1, 2, 3], [2, 4]); + bench('lodash/intersection', () => { + intersectionLodash(array1, array2); }); }); diff --git a/benchmarks/intersectionBy.bench.ts b/benchmarks/intersectionBy.bench.ts index f06e2d030..17bc7ca50 100644 --- a/benchmarks/intersectionBy.bench.ts +++ b/benchmarks/intersectionBy.bench.ts @@ -3,14 +3,14 @@ import { intersectionBy as intersectionByToolkit } from 'es-toolkit'; import { intersectionBy as intersectionByLodash } from 'lodash'; describe('intersectionBy', () => { - bench('es-toolkit', () => { + bench('es-toolkit/intersectionBy', () => { const array1 = [{ id: 1 }, { id: 2 }, { id: 3 }]; const array2 = [{ id: 2 }, { id: 4 }]; const mapper = item => item.id; intersectionByToolkit(array1, array2, mapper); }); - bench('lodash', () => { + bench('lodash/intersectionBy', () => { const array1 = [{ id: 1 }, { id: 2 }, { id: 3 }]; const array2 = [{ id: 2 }, { id: 4 }]; const mapper = item => item.id; diff --git a/benchmarks/intersectionWith.bench.ts b/benchmarks/intersectionWith.bench.ts index 64ae45884..1f4471758 100644 --- a/benchmarks/intersectionWith.bench.ts +++ b/benchmarks/intersectionWith.bench.ts @@ -3,14 +3,14 @@ import { intersectionWith as intersectionWithToolkit } from 'es-toolkit'; import { intersectionWith as intersectionWithLodash } from 'lodash'; describe('intersectionWith', () => { - bench('es-toolkit', () => { + bench('es-toolkit/intersectionWith', () => { const array1 = [{ id: 1 }, { id: 2 }, { id: 3 }]; const array2 = [{ id: 2 }, { id: 4 }]; const areItemsEqual = (a, b) => a.id === b.id; intersectionWithToolkit(array1, array2, areItemsEqual); }); - bench('lodash', () => { + bench('lodash/intersectionWith', () => { const array1 = [{ id: 1 }, { id: 2 }, { id: 3 }]; const array2 = [{ id: 2 }, { id: 4 }]; const areItemsEqual = (a, b) => a.id === b.id; diff --git a/benchmarks/isNil.bench.ts b/benchmarks/isNil.bench.ts index 308ec78cc..b3630525c 100644 --- a/benchmarks/isNil.bench.ts +++ b/benchmarks/isNil.bench.ts @@ -3,14 +3,14 @@ import { isNil as isNilToolkit } from 'es-toolkit'; import { isNil as isNilLodash } from 'lodash'; describe('isNil', () => { - bench('es-toolkit', () => { + bench('es-toolkit/isNil', () => { isNilToolkit(null); isNilToolkit(undefined); isNilToolkit(123); isNilToolkit([1, 2, 3]); }); - bench('lodash', () => { + bench('lodash/isNil', () => { isNilLodash(null); isNilLodash(undefined); isNilLodash(123); diff --git a/benchmarks/maxBy.bench.ts b/benchmarks/maxBy.bench.ts new file mode 100755 index 000000000..ed3b4ccf9 --- /dev/null +++ b/benchmarks/maxBy.bench.ts @@ -0,0 +1,23 @@ +import { bench, describe } from 'vitest'; +import { maxBy as maxByToolkit } from 'es-toolkit'; +import { maxBy as maxByLodash } from 'lodash'; + +describe('maxBy', () => { + bench('es-toolkit/maxBy', () => { + const people = [ + { name: 'Mark', age: 25 }, + { name: 'Nunu', age: 30 }, + { name: 'Overmars', age: 20 }, + ]; + maxByToolkit(people, person => person.age); + }); + + bench('lodash/maxBy', () => { + const people = [ + { name: 'Mark', age: 25 }, + { name: 'Nunu', age: 30 }, + { name: 'Overmars', age: 20 }, + ]; + maxByLodash(people, person => person.age); + }); +}); diff --git a/benchmarks/minBy.bench.ts b/benchmarks/minBy.bench.ts new file mode 100644 index 000000000..2cd7f8735 --- /dev/null +++ b/benchmarks/minBy.bench.ts @@ -0,0 +1,23 @@ +import { bench, describe } from 'vitest'; +import { minBy as minByToolkit } from 'es-toolkit'; +import { minBy as minByLodash } from 'lodash'; + +describe('minBy', () => { + bench('es-toolkit/minBy', () => { + const people = [ + { name: 'Mark', age: 30 }, + { name: 'Nunu', age: 20 }, + { name: 'Overmars', age: 35 }, + ]; + minByToolkit(people, person => person.age); + }); + + bench('lodash/minBy', () => { + const people = [ + { name: 'Mark', age: 30 }, + { name: 'Nunu', age: 20 }, + { name: 'Overmars', age: 35 }, + ]; + minByLodash(people, person => person.age); + }); +}); diff --git a/benchmarks/omit.bench.ts b/benchmarks/omit.bench.ts index 3ef5851d0..ad972e049 100644 --- a/benchmarks/omit.bench.ts +++ b/benchmarks/omit.bench.ts @@ -3,11 +3,11 @@ import { omit as omitToolkit } from 'es-toolkit'; import { omit as omitLodash } from 'lodash'; describe('omit', () => { - bench('es-toolkit', () => { + bench('es-toolkit/omit', () => { omitToolkit({ foo: 1, bar: 2, baz: 3 }, ['foo', 'bar']); }); - bench('lodash', () => { + bench('lodash/omit', () => { omitLodash({ foo: 1, bar: 2, baz: 3 }, ['foo', 'bar']); }); }); diff --git a/benchmarks/omitBy.bench.ts b/benchmarks/omitBy.bench.ts index 790379ded..2f5ca3f61 100644 --- a/benchmarks/omitBy.bench.ts +++ b/benchmarks/omitBy.bench.ts @@ -3,13 +3,13 @@ import { omitBy as omitByLodash } from 'lodash'; import { bench, describe } from 'vitest'; describe('omitBy', () => { - bench('es-toolkit', () => { + bench('es-toolkit/omitBy', () => { const obj = { a: 1, b: 'omit', c: 3 }; const shouldOmit = (value: number | string) => typeof value === 'string'; omitByToolkit(obj, shouldOmit); }); - bench('lodash', () => { + bench('lodash/omitBy', () => { const obj = { a: 1, b: 'omit', c: 3 }; const shouldOmit = (value: number | string) => typeof value === 'string'; omitByLodash(obj, shouldOmit); diff --git a/benchmarks/partition.bench.ts b/benchmarks/partition.bench.ts index e2f9bc09e..d23c44e9a 100644 --- a/benchmarks/partition.bench.ts +++ b/benchmarks/partition.bench.ts @@ -3,11 +3,11 @@ import { partition as partitionToolkit } from 'es-toolkit'; import { partition as partitionLodash } from 'lodash'; describe('partition', () => { - bench('es-toolkit', () => { + bench('es-toolkit/partition', () => { partitionToolkit([1, 2, 3, 4, 5], x => x < 3); }); - bench('lodash', () => { + bench('lodash/partition', () => { partitionLodash([1, 2, 3], x => x < 3); }); }); diff --git a/benchmarks/pick.bench.ts b/benchmarks/pick.bench.ts index 1d3e17b75..b0d70d14d 100644 --- a/benchmarks/pick.bench.ts +++ b/benchmarks/pick.bench.ts @@ -3,11 +3,11 @@ import { pick as pickToolkit } from 'es-toolkit'; import { pick as pickLodash } from 'lodash'; describe('pick', () => { - bench('es-toolkit', () => { + bench('es-toolkit/pick', () => { pickToolkit({ foo: 1, bar: 2, baz: 3 }, ['foo', 'bar']); }); - bench('lodash', () => { + bench('lodash/pick', () => { pickLodash({ foo: 1, bar: 2, baz: 3 }, ['foo', 'bar']); }); }); diff --git a/benchmarks/random.bench.ts b/benchmarks/random.bench.ts new file mode 100644 index 000000000..898ecacfa --- /dev/null +++ b/benchmarks/random.bench.ts @@ -0,0 +1,13 @@ +import { bench, describe } from 'vitest'; +import { random as randomToolkit } from 'es-toolkit'; +import { random as randomLodash } from 'lodash'; + +describe('random', () => { + bench('es-toolkit/random', () => { + randomToolkit(1, 10); + }); + + bench('lodash/random', () => { + randomLodash(1, 10, true); + }); +}); diff --git a/benchmarks/round.bench.ts b/benchmarks/round.bench.ts index fbb9e5ca5..df03b91a3 100644 --- a/benchmarks/round.bench.ts +++ b/benchmarks/round.bench.ts @@ -3,11 +3,11 @@ import { round as roundToolkit } from 'es-toolkit'; import { round as roundLodash } from 'lodash'; describe('round', () => { - bench('es-toolkit', () => { + bench('es-toolkit/round', () => { roundToolkit(1.2345, 2); }); - bench('lodash', () => { + bench('lodash/round', () => { roundLodash(1.2345, 2); }); }); diff --git a/benchmarks/sample.bench.ts b/benchmarks/sample.bench.ts index 76d2eed3b..c4819b490 100644 --- a/benchmarks/sample.bench.ts +++ b/benchmarks/sample.bench.ts @@ -3,12 +3,12 @@ import { sample as sampleToolkit } from 'es-toolkit'; import { sample as sampleLodash } from 'lodash'; describe('sample', () => { - bench('es-toolkit', () => { + bench('es-toolkit/sample', () => { const array = [1, 2, 3, 4, 5]; sampleToolkit(array); }); - bench('lodash', () => { + bench('lodash/sample', () => { const array = [1, 2, 3, 4, 5]; sampleLodash(array); }); diff --git a/benchmarks/shuffle.bench.ts b/benchmarks/shuffle.bench.ts index c5c8077e7..02d4f711f 100644 --- a/benchmarks/shuffle.bench.ts +++ b/benchmarks/shuffle.bench.ts @@ -3,12 +3,12 @@ import { shuffle as shuffleToolkit } from 'es-toolkit'; import { shuffle as shuffleLodash } from 'lodash'; describe('shuffle', () => { - bench('es-toolkit', () => { + bench('es-toolkit/shuffle', () => { const array = [1, 2, 3, 4, 5]; shuffleToolkit(array); }); - bench('lodash', () => { + bench('lodash/shuffle', () => { const array = [1, 2, 3, 4, 5]; shuffleLodash(array); }); diff --git a/benchmarks/sum.bench.ts b/benchmarks/sum.bench.ts index 7b3ed2959..5574df2ad 100644 --- a/benchmarks/sum.bench.ts +++ b/benchmarks/sum.bench.ts @@ -3,11 +3,11 @@ import { sum as sumToolkit } from 'es-toolkit'; import { sum as sumLodash } from 'lodash'; describe('sum', () => { - bench('es-toolkit', () => { + bench('es-toolkit/sum', () => { sumToolkit([1, 2, 3]); }); - bench('lodash', () => { + bench('lodash/sum', () => { sumLodash([1, 2, 3]); }); }); diff --git a/benchmarks/take.bench.ts b/benchmarks/take.bench.ts index 3a1088c69..30cd3e876 100644 --- a/benchmarks/take.bench.ts +++ b/benchmarks/take.bench.ts @@ -3,11 +3,11 @@ import { take as takeToolkit } from 'es-toolkit'; import { take as takeLodash } from 'lodash'; describe('take', () => { - bench('es-toolkit', () => { + bench('es-toolkit/take', () => { takeToolkit([1, 2, 3, 4], 2); }); - bench('lodash', () => { + bench('lodash/take', () => { takeLodash([1, 2, 3, 4], 2); }); }); diff --git a/benchmarks/takeRight.bench.ts b/benchmarks/takeRight.bench.ts index ce12dbbd1..ed3c35873 100644 --- a/benchmarks/takeRight.bench.ts +++ b/benchmarks/takeRight.bench.ts @@ -3,11 +3,11 @@ import { takeRight as takeRightToolkit } from 'es-toolkit'; import { takeRight as takeRightLodash } from 'lodash'; describe('takeRight', () => { - bench('es-toolkit', () => { + bench('es-toolkit/takeRight', () => { takeRightToolkit([1, 2, 3, 4], 2); }); - bench('lodash', () => { + bench('lodash/takeRight', () => { takeRightLodash([1, 2, 3, 4], 2); }); }); diff --git a/benchmarks/takeRightWhile.bench.ts b/benchmarks/takeRightWhile.bench.ts index 29dcf929f..292786e26 100644 --- a/benchmarks/takeRightWhile.bench.ts +++ b/benchmarks/takeRightWhile.bench.ts @@ -3,11 +3,11 @@ import { takeRightWhile as takeRightWhileToolkit } from 'es-toolkit'; import { takeRightWhile as takeRightWhileLodash } from 'lodash'; describe('takeRightWhile', () => { - bench('es-toolkit', () => { + bench('es-toolkit/takeRightWhile', () => { takeRightWhileToolkit([5, 4, 3, 2, 1], n => n < 4); }); - bench('lodash', () => { + bench('lodash/takeRightWhile', () => { takeRightWhileLodash([5, 4, 3, 2, 1], n => n < 4); }); }); diff --git a/benchmarks/takeWhile.bench.ts b/benchmarks/takeWhile.bench.ts index 14e6fb88e..65dfbec97 100644 --- a/benchmarks/takeWhile.bench.ts +++ b/benchmarks/takeWhile.bench.ts @@ -3,11 +3,11 @@ import { takeWhile as takeWhileToolkit } from 'es-toolkit'; import { takeWhile as takeWhileLodash } from 'lodash'; describe('takeWhile', () => { - bench('es-toolkit', () => { + bench('es-toolkit/takeWhile', () => { takeWhileToolkit([5, 4, 3, 2, 1], n => n < 4); }); - bench('lodash', () => { + bench('lodash/takeWhile', () => { takeWhileLodash([5, 4, 3, 2, 1], n => n < 4); }); }); diff --git a/benchmarks/union.bench.ts b/benchmarks/union.bench.ts index 94934a070..330c664c0 100644 --- a/benchmarks/union.bench.ts +++ b/benchmarks/union.bench.ts @@ -3,13 +3,13 @@ import { union as unionLodash } from 'lodash'; import { bench, describe } from 'vitest'; describe('union', () => { - bench('es-toolkit', () => { + bench('es-toolkit/union', () => { const array1 = [1, 2, 3]; const array2 = [3, 4, 5]; unionToolkit(array1, array2); }); - bench('lodash', () => { + bench('lodash/union', () => { const array1 = [1, 2, 3]; const array2 = [3, 4, 5]; unionLodash(array1, array2); diff --git a/benchmarks/unionBy.bench.ts b/benchmarks/unionBy.bench.ts index d73ab6ccb..974c2db84 100644 --- a/benchmarks/unionBy.bench.ts +++ b/benchmarks/unionBy.bench.ts @@ -3,11 +3,11 @@ import { unionBy as unionByToolkit } from 'es-toolkit'; import { unionBy as unionByLodash } from 'lodash'; describe('unionBy', () => { - bench('es-toolkit', () => { + bench('es-toolkit/unionBy', () => { unionByToolkit([{ id: 1 }, { id: 2 }], [{ id: 2 }, { id: 3 }], x => x.id); }); - bench('lodash', () => { + bench('lodash/unionBy', () => { unionByLodash([{ id: 1 }, { id: 2 }], [{ id: 2 }, { id: 3 }], x => x.id); }); }); diff --git a/benchmarks/unionWith.bench.ts b/benchmarks/unionWith.bench.ts index 4766460c1..d3219b427 100644 --- a/benchmarks/unionWith.bench.ts +++ b/benchmarks/unionWith.bench.ts @@ -3,14 +3,14 @@ import { unionWith as unionWithToolkit } from 'es-toolkit'; import { unionWith as unionWithLodash } from 'lodash'; describe('unionWith', () => { - bench('es-toolkit', () => { + bench('es-toolkit/unionWith', () => { const array1 = [{ id: 1 }, { id: 2 }]; const array2 = [{ id: 2 }, { id: 3 }]; const areItemsEqual = (a, b) => a.id === b.id; unionWithToolkit(array1, array2, areItemsEqual); }); - bench('lodash', () => { + bench('lodash/unionWith', () => { const array1 = [{ id: 1 }, { id: 2 }]; const array2 = [{ id: 2 }, { id: 3 }]; const areItemsEqual = (a, b) => a.id === b.id; diff --git a/benchmarks/uniq.bench.ts b/benchmarks/uniq.bench.ts index 6ba7429f1..ca68bd1ef 100644 --- a/benchmarks/uniq.bench.ts +++ b/benchmarks/uniq.bench.ts @@ -3,11 +3,11 @@ import { uniq as uniqToolkit } from 'es-toolkit'; import { uniq as uniqLodash } from 'lodash'; describe('uniq', () => { - bench('es-toolkit', () => { + bench('es-toolkit/uniq', () => { uniqToolkit([11, 2, 3, 44, 11, 2, 3]); }); - bench('lodash', () => { + bench('lodash/uniq', () => { uniqLodash([11, 2, 3, 44, 11, 2, 3]); }); }); diff --git a/benchmarks/uniqBy.bench.ts b/benchmarks/uniqBy.bench.ts new file mode 100644 index 000000000..a3a1fd281 --- /dev/null +++ b/benchmarks/uniqBy.bench.ts @@ -0,0 +1,26 @@ +import { bench, describe } from 'vitest'; +import { uniqBy as uniqByToolkit } from 'es-toolkit'; +import { uniqBy as uniqByLodash } from 'lodash'; +import { randomInt } from 'crypto'; + +describe('uniqBy, small arrays', () => { + bench('es-toolkit/uniqBy', () => { + uniqByToolkit([2.1, 1.2, 2.3], Math.floor); + }); + + bench('lodash/uniqBy', () => { + uniqByLodash([2.1, 1.2, 2.3], Math.floor); + }); +}); + +describe('uniqBy, large arrays', () => { + const array = Array.from({ length: 10000 }).map(() => randomInt(0, 10000)); + + bench('es-toolkit/uniqBy', () => { + uniqByToolkit(array, Math.floor); + }); + + bench('lodash/uniqBy', () => { + uniqByLodash(array, Math.floor); + }); +}); diff --git a/benchmarks/uniqWith.bench.ts b/benchmarks/uniqWith.bench.ts new file mode 100644 index 000000000..78834855e --- /dev/null +++ b/benchmarks/uniqWith.bench.ts @@ -0,0 +1,27 @@ +import { bench, describe } from 'vitest'; +import { uniqWith as uniqWithToolkit } from 'es-toolkit'; +import { uniqWith as uniqWithLodash } from 'lodash'; +import { randomInt } from 'crypto'; + +describe('uniqWith, small arrays', () => { + bench('es-toolkit/uniqWith', () => { + uniqWithToolkit([2.1, 1.2, 2.3], (x, y) => Math.floor(x) === Math.floor(y)); + }); + + bench('lodash/uniqWith', () => { + uniqWithLodash([2.1, 1.2, 2.3], (x, y) => Math.floor(x) === Math.floor(y)); + }); +}); + +describe('uniqWith, large arrays', () => { + const array = Array.from({ length: 10000 }).map(() => randomInt(0, 10000)); + const comparator = (x, y) => Math.floor(x) === Math.floor(y); + + bench('es-toolkit/uniqWith', () => { + uniqWithToolkit(array, comparator); + }); + + bench('lodash/uniqWith', () => { + uniqWithLodash(array, comparator); + }); +}); diff --git a/benchmarks/xor.bench.ts b/benchmarks/xor.bench.ts index b1636e908..5fe36032f 100644 --- a/benchmarks/xor.bench.ts +++ b/benchmarks/xor.bench.ts @@ -3,11 +3,11 @@ import { xor as xorToolkit } from 'es-toolkit'; import { xor as xorLodash } from 'lodash'; describe('xor', () => { - bench('es-toolkit', () => { + bench('es-toolkit/xor', () => { xorToolkit([1, 2, 3, 4], [3, 4, 5, 6]); }); - bench('lodash', () => { + bench('lodash/xor', () => { xorLodash([1, 2, 3, 4], [3, 4, 5, 6]); }); }); diff --git a/benchmarks/xorBy.bench.ts b/benchmarks/xorBy.bench.ts index 2992f7c3b..3c0f35065 100644 --- a/benchmarks/xorBy.bench.ts +++ b/benchmarks/xorBy.bench.ts @@ -3,12 +3,12 @@ import { xorBy as xorByToolkit } from 'es-toolkit'; import { xorBy as xorByLodash } from 'lodash'; describe('xorBy', () => { - bench('es-toolkit', () => { + bench('es-toolkit/xorBy', () => { const idMapper = obj => obj.id; xorByToolkit([{ id: 1 }, { id: 2 }], [{ id: 2 }, { id: 3 }], idMapper); }); - bench('lodash', () => { + bench('lodash/xorBy', () => { const idMapper = obj => obj.id; xorByLodash([{ id: 1 }, { id: 2 }], [{ id: 2 }, { id: 3 }], idMapper); }); diff --git a/benchmarks/xorWith.bench.ts b/benchmarks/xorWith.bench.ts index 2822c24c6..f3cfec7df 100644 --- a/benchmarks/xorWith.bench.ts +++ b/benchmarks/xorWith.bench.ts @@ -3,11 +3,11 @@ import { xorWith as xorWithLodash } from 'lodash'; import { bench, describe } from 'vitest'; describe('xorWith', () => { - bench('es-toolkit', () => { + bench('es-toolkit/xorWith', () => { xorWithToolkit([{ id: 1 }, { id: 2 }], [{ id: 2 }, { id: 3 }], (x, y) => x.id === y.id); }); - bench('lodash', () => { + bench('lodash/xorWith', () => { xorWithLodash([{ id: 1 }, { id: 2 }], [{ id: 2 }, { id: 3 }], (x, y) => x.id === y.id); }); }); diff --git a/benchmarks/zip.bench.ts b/benchmarks/zip.bench.ts index f3e3f2180..dd103d505 100644 --- a/benchmarks/zip.bench.ts +++ b/benchmarks/zip.bench.ts @@ -3,11 +3,11 @@ import { zip as zipToolkit } from 'es-toolkit'; import { zip as zipLodash } from 'lodash'; describe('zip', () => { - bench('es-toolkit', () => { + bench('es-toolkit/zip', () => { zipToolkit([1, 2, 3, 4], [3, 4, 5, 6]); }); - bench('lodash', () => { + bench('lodash/zip', () => { zipLodash([1, 2, 3, 4], [3, 4, 5, 6]); }); }); diff --git a/benchmarks/zipWith.bench.ts b/benchmarks/zipWith.bench.ts index 41a3844f8..9e5e8a769 100644 --- a/benchmarks/zipWith.bench.ts +++ b/benchmarks/zipWith.bench.ts @@ -3,14 +3,14 @@ import { zipWith as zipWithToolkit } from 'es-toolkit'; import { zipWith as zipWithLodash } from 'lodash'; describe('zipWith', () => { - bench('es-toolkit', () => { + bench('es-toolkit/zipWith', () => { const arr1 = [1, 2]; const arr2 = [3, 4]; const arr3 = [5, 6]; zipWithToolkit(arr1, arr2, arr3, (a, b, c) => `${a}${b}${c}`); }); - bench('lodash', () => { + bench('lodash/zipWith', () => { const arr1 = [1, 2]; const arr2 = [3, 4]; const arr3 = [5, 6]; diff --git a/docs/.vitepress/en.mts b/docs/.vitepress/en.mts index d022b3846..da1f893c8 100644 --- a/docs/.vitepress/en.mts +++ b/docs/.vitepress/en.mts @@ -70,6 +70,8 @@ function sidebar(): DefaultTheme.Sidebar { { text: 'unionBy', link: '/reference/array/unionBy' }, { text: 'unionWith', link: '/reference/array/unionWith' }, { text: 'uniq', link: '/reference/array/uniq' }, + { text: 'uniqBy', link: '/reference/array/uniqBy' }, + { text: 'uniqWith', link: '/reference/array/uniqWith' }, { text: 'xor', link: '/reference/array/xor' }, { text: 'xorBy', link: '/reference/array/xorBy' }, { text: 'xorWith', link: '/reference/array/xorWith' }, @@ -90,6 +92,8 @@ function sidebar(): DefaultTheme.Sidebar { text: 'Math Utilities', items: [ { text: 'clamp', link: '/reference/math/clamp' }, + { text: 'random', link: '/reference/math/random' }, + { text: 'randomInt', link: '/reference/math/randomInt' }, { text: 'round', link: '/reference/math/round' }, { text: 'sum', link: '/reference/math/sum' }, ], diff --git a/docs/.vitepress/ko.mts b/docs/.vitepress/ko.mts index 8d77a2956..cbb243d41 100644 --- a/docs/.vitepress/ko.mts +++ b/docs/.vitepress/ko.mts @@ -69,6 +69,8 @@ function sidebar(): DefaultTheme.Sidebar { { text: 'unionBy', link: '/ko/reference/array/unionBy' }, { text: 'unionWith', link: '/ko/reference/array/unionWith' }, { text: 'uniq', link: '/ko/reference/array/uniq' }, + { text: 'uniqBy', link: '/ko/reference/array/uniqBy' }, + { text: 'uniqWith', link: '/ko/reference/array/uniqWith' }, { text: 'xor', link: '/ko/reference/array/xor' }, { text: 'xorBy', link: '/ko/reference/array/xorBy' }, { text: 'xorWith', link: '/ko/reference/array/xorWith' }, @@ -89,6 +91,8 @@ function sidebar(): DefaultTheme.Sidebar { text: '숫자', items: [ { text: 'clamp', link: '/ko/reference/math/clamp' }, + { text: 'random', link: '/ko/reference/math/random' }, + { text: 'randomInt', link: '/ko/reference/math/randomInt' }, { text: 'round', link: '/ko/reference/math/round' }, { text: 'sum', link: '/ko/reference/math/sum' }, ], diff --git a/docs/ko/reference/array/take.md b/docs/ko/reference/array/take.md index a5e2659e7..178af1d18 100644 --- a/docs/ko/reference/array/take.md +++ b/docs/ko/reference/array/take.md @@ -1,8 +1,8 @@ # take -입력 배열 arr에서 처음 count개의 요소를 포함하는 새로운 배열을 반환해요. +입력 배열 `arr`에서 처음 `count`개의 요소를 포함하는 새로운 배열을 반환해요. -만약 count가 arr의 길이보다 크면, 전체 배열을 반환해요. +만약 `count`가 `arr`의 길이보다 크면, 전체 배열을 반환해요. ## 인터페이스 @@ -17,7 +17,7 @@ function take(arr: T[], count: number): T[]; ### 반환 값 -(T[]): arr에서 처음 `count`개의 요소를 포함하는 새로운 배열이에요. +(`T[]`): `arr`에서 처음 `count`개의 요소를 포함하는 새로운 배열이에요. ## 예시 diff --git a/docs/ko/reference/array/uniqBy.md b/docs/ko/reference/array/uniqBy.md new file mode 100644 index 000000000..e38a6a706 --- /dev/null +++ b/docs/ko/reference/array/uniqBy.md @@ -0,0 +1,25 @@ +# uniqBy + +`mapper` 함수가 반환하는 값을 기준으로, 배열 내 요소들의 중복을 제거해요. + +## 인터페이스 + +```typescript +function uniqBy(arr: T[], mapper: (item: T) => U): T[]; +``` + +### 파라미터 + +- `arr` (`T[]`): 중복을 제거할 배열. +- `mapper` (`(item: T) => U`): 비교하기 위해 요소를 새로운 값으로 변환할 함수. + +### 반환 값 + +(`T[]`): `mapper` 함수가 반환하는 값을 기준으로 중복이 제거된 배열. + +## 예시 + +```typescript +uniqBy([1.2, 1.5, 2.1, 3.2, 5.7, 5.3, 7.19], Math.floor); +// [1.2, 2.1, 3.3, 5.7, 7.19] +``` diff --git a/docs/ko/reference/array/uniqWith.md b/docs/ko/reference/array/uniqWith.md new file mode 100644 index 000000000..0ab6a0ad8 --- /dev/null +++ b/docs/ko/reference/array/uniqWith.md @@ -0,0 +1,25 @@ +# uniqWith + +두 요소가 일치하는지 여부를 판단하는 커스텀 함수를 기준으로, 배열 내 요소들의 중복을 제거해요. + +## 인터페이스 + +```typescript +function uniqWith(arr: T[], areItemsEqual: (item1: T, item2: T) => boolean): T[]; +``` + +### 파라미터 + +- `arr` (`T[]`): 중복을 제거할 배열. +- `areItemsEqual` (`(x: T, y: T) => boolean`): 두 요소가 일치하는지 판단하는 일치 함수예요. 두 요소가 일치한다면 `true`를, 일치하지 않는다면 `false`를 반환하게 해주세요. + +### 반환 값 + +(`T[]`): 커스텀 일치 함수의 반환 값을 기준으로, 중복이 제거된 새로운 배열. + +## 예시 + +```typescript +uniqWith([1.2, 1.5, 2.1, 3.2, 5.7, 5.3, 7.19], (a, b) => Math.abs(a - b) < 1); +// [1.2, 3.2, 5.7, 7.19] +``` diff --git a/docs/ko/reference/function/debounce.md b/docs/ko/reference/function/debounce.md index 2339ba272..395714036 100644 --- a/docs/ko/reference/function/debounce.md +++ b/docs/ko/reference/function/debounce.md @@ -7,13 +7,19 @@ debounce된 함수는 또한 대기 중인 실행을 취소하는 `cancel` 메 ## 인터페이스 ```typescript -function debounce void>(func: F, debounceMs: number): F & { cancel: () => void }; +function debounce void>( + func: F, + debounceMs: number, + options?: DebounceOptions +): F & { cancel: () => void }; ``` ### 파라미터 - `func` (`F`): debounce된 함수를 만들 함수. - `debounceMs`(`number`): debounce로 지연시킬 밀리초. +- `options` (`DebounceOptions`, optional): 옵션 객체. + - `signal` (`AbortSignal`, optional): debounce된 함수를 취소하기 위한 선택적 `AbortSignal`. ### 결괏값 @@ -21,6 +27,8 @@ function debounce void>(func: F, debounceMs: numbe ## 예시 +### 기본 사용법 + ```typescript const debouncedFunction = debounce(() => { console.log('실행됨'); @@ -32,3 +40,23 @@ debouncedFunction(); // 이전 호출이 취소되었으므로, 아무것도 로깅하지 않아요 debouncedFunction.cancel(); ``` + +### AbortSignal 사용법 + +```typescript +const controller = new AbortController(); +const signal = controller.signal; +const debouncedWithSignalFunction = debounce( + () => { + console.log('Function executed'); + }, + 1000, + { signal } +); + +// 1초 안에 다시 호출되지 않으면, '실행됨'을 로깅해요 +debouncedWithSignalFunction(); + +// debounce 함수 호출을 취소해요 +controller.abort(); +``` diff --git a/docs/ko/reference/math/maxBy.md b/docs/ko/reference/math/maxBy.md new file mode 100644 index 000000000..112494ede --- /dev/null +++ b/docs/ko/reference/math/maxBy.md @@ -0,0 +1,27 @@ +# maxBy + +주어진 배열 내의 요소들 중에서 조건에 따라 최대값을 가지는 첫 번째 요소를 선택하는 함수에요. + +배열이 비어있지 않다면 조건에 따라 최대값을 가지는 첫 번째 요소를 반환하고, 비어있다면 `undefined`를 반환해요. + +## 인터페이스 + +```typescript +function maxBy(elements: T[], selector: (element: T) => number): T; +``` + +### 파라미터 + +- `elements`: 검색할 요소들의 배열 +- `selector`: 요소를 받아서 객체의 속성을 반환하는 함수 + +### 반환값 + +함수의 최대값을 가지는 배열의 첫 번째 요소. 만약 배열이 비어있다면 `undefined`를 반환해요. + +### 예시 + +```typescript +maxBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // 결과: { a: 3 } +maxBy([], x => x.a); // 결과: undefined +``` diff --git a/docs/ko/reference/math/minBy.md b/docs/ko/reference/math/minBy.md new file mode 100644 index 000000000..3234ac6a3 --- /dev/null +++ b/docs/ko/reference/math/minBy.md @@ -0,0 +1,27 @@ +# maxBy + +주어진 배열 내의 요소들 중에서 조건에 따라 최솟값을 가지는 첫 번째 요소를 선택하는 함수에요. + +배열이 비어있지 않다면 조건에 따라 최솟값을 가지는 첫 번째 요소를 반환하고, 비어있다면 `undefined`를 반환해요. + +## 인터페이스 + +```typescript +function minBy(elements: T[], selector: (element: T) => number): T; +``` + +### 파라미터 + +- `elements`: 검색할 요소들의 배열 +- `selector`: 요소를 받아서 객체의 속성을 반환하는 함수 + +### 반환값 + +함수의 최솟값을 가지는 배열의 첫 번째 요소. 만약 배열이 비어있다면 `undefined`를 반환해요. + +### 예시 + +```typescript +maxBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // 결과: { a: 3 } +maxBy([], x => x.a); // 결과: undefined +``` diff --git a/docs/ko/reference/math/random.md b/docs/ko/reference/math/random.md new file mode 100644 index 000000000..a52e635a0 --- /dev/null +++ b/docs/ko/reference/math/random.md @@ -0,0 +1,26 @@ +# random + +주어진 범위 내에서 무작위 숫자를 생성해요. 여기에서 숫자는 정수뿐만 아니라 소수점 있는 숫자도 포함해요. + +## 인터페이스 + +```typescript +function random(minimum: number, maximum: number): number; +``` + +### 파라미터 + +- `minimum` (`number`): 무작위 숫자를 생성할 최솟값(포함)이에요. +- `maximum` (`number`): 무작위 숫자를 생성할 최댓값(미포함)이에요. + +### 반환 값 + +- (`number`): 지정된 범위 내에서 무작위 숫자를 반환해요. 숫자는 정수뿐만 아니라 소수점 있는 숫자도 포함해요. + +## 예시 + +```typescript +const result1 = random(0, 5); // 0과 5사이의 무작위 부동 소수점 숫자를 반환해요. +const result2 = random(5, 0); // 최솟값이 최댓값보다 크면 오류가 발생해요. +const result3 = random(5, 5); // 최솟값이 최댓값과 같으면 오류가 발생해요. +``` diff --git a/docs/ko/reference/math/randomInt.md b/docs/ko/reference/math/randomInt.md new file mode 100644 index 000000000..bee52c023 --- /dev/null +++ b/docs/ko/reference/math/randomInt.md @@ -0,0 +1,26 @@ +# randomInt + +주어진 범위 내에서 무작위 정수를 생성해요. + +## 인터페이스 + +```typescript +function randomInt(minimum: number, maximum: number): number; +``` + +### 파라미터 + +- `minimum` (`number`): 무작위 정수를 생성할 최솟값(포함)이에요. +- `maximum` (`number`): 무작위 정수를 생성할 최댓값(미포함)이에요. + +### 반환 값 + +- (`number`): 지정된 범위 내에서 무작위 정수를 반환해요. + +## 예시 + +```typescript +const result1 = randomInt(0, 5); // 0과 5사이의 무작위 정수를 반환해요. +const result2 = randomInt(5, 0); // 최솟값이 최댓값보다 크면 오류가 발생해요. +const result3 = randomInt(5, 5); // 최솟값이 최댓값과 같으면 오류가 발생해요. +``` diff --git a/docs/ko/reference/math/round.md b/docs/ko/reference/math/round.md index 197218050..313b14bec 100644 --- a/docs/ko/reference/math/round.md +++ b/docs/ko/reference/math/round.md @@ -25,4 +25,5 @@ function round(value: number, precision?: number): number; const result1 = round(1.2345); // result1은 1이 되어요. const result2 = round(1.2345, 2); // result2는 1.23이 되어요. const result3 = round(1.2345, 3); // result3는 1.235가 되어요. +const result4 = round(1.2345, 3.1); // precision이 integer가 아니면 오류를 반환해요. ``` diff --git a/docs/ko/reference/object/omitBy.md b/docs/ko/reference/object/omitBy.md index 883fcc42c..16f6c0067 100644 --- a/docs/ko/reference/object/omitBy.md +++ b/docs/ko/reference/object/omitBy.md @@ -7,13 +7,16 @@ ## 인터페이스 ```typescript -function omitBy>(obj: T, shouldOmit: (value: any, key: string) => boolean): Partial; +function omitBy>( + obj: T, + shouldOmit: (value: T[keyof T], key: keyof T) => boolean +): Partial; ``` ### 파라미터 - `obj` (`T`): 프로퍼티를 생략할 객체예요. -- `shouldOmit` (`(value: any, key: string) => boolean`): 프로퍼티를 생략할지 결정하는 조건 함수예요. 이 함수는 프로퍼티의 키와 값을 인자로 받아, 프로퍼티를 생략해야 하면 true, 그렇지 않으면 false를 반환해요. +- `shouldOmit` (`(value: T[keyof T], key: keyof T) => boolean`): 프로퍼티를 생략할지 결정하는 조건 함수예요. 이 함수는 프로퍼티의 키와 값을 인자로 받아, 프로퍼티를 생략해야 하면 true, 그렇지 않으면 false를 반환해요. ### 반환 값 diff --git a/docs/ko/reference/object/pickBy.md b/docs/ko/reference/object/pickBy.md index 37264eb29..ee4d62b48 100644 --- a/docs/ko/reference/object/pickBy.md +++ b/docs/ko/reference/object/pickBy.md @@ -9,14 +9,14 @@ ```typescript function pickBy>( obj: T, - shouldPick: (value: T[keyof T], key: string) => boolean + shouldPick: (value: T[keyof T], key: keyof T) => boolean ): Partial; ``` ### 파라미터 - `obj` (`T`): 프로퍼티를 선택할 객체예요. -- `shouldPick` (`(value: T[keyof T], key: string) => boolean`): 프로퍼티를 선택할지를 결정하는 조건 함수예요. 이 함수는 프로퍼티의 키와 값을 인수로 받아, 프로퍼티를 선택해야 하면 true, 그렇지 않으면 false를 반환해요. +- `shouldPick` (`(value: T[keyof T], key: keyof T) => boolean`): 프로퍼티를 선택할지를 결정하는 조건 함수예요. 이 함수는 프로퍼티의 키와 값을 인수로 받아, 프로퍼티를 선택해야 하면 true, 그렇지 않으면 false를 반환해요. ### 반환 값 diff --git a/docs/ko/reference/predicate/isUndefined.md b/docs/ko/reference/predicate/isUndefined.md index 738482814..28984d0ad 100644 --- a/docs/ko/reference/predicate/isUndefined.md +++ b/docs/ko/reference/predicate/isUndefined.md @@ -1,6 +1,6 @@ # isUndefined -주어진 값이 undefined인지 확인해요. +주어진 값이 `undefined`인지 확인해요. 이 함수는 주어진 값이 `undefined` 인지 엄격 일치 (===) 기준으로 확인합니다. 값이 `undefined` 이면 `true`, 아니면 `false` 를 반환해요. @@ -15,11 +15,11 @@ function isUndefined(x: unknown): x is undefined; ### 파라미터 -- `x` (`unknown`): undefined인지 확인할 값 +- `x` (`unknown`): `undefined`인지 확인할 값 ### 반환 값 -(`x is undefined`): 값이 undefined이면 true, 아니면 false. +(`x is undefined`): 값이 `undefined`이면 `true`, 아니면 `false`. ## 예시 diff --git a/docs/ko/reference/promise/delay.md b/docs/ko/reference/promise/delay.md index 03c3411f4..a38415fe9 100644 --- a/docs/ko/reference/promise/delay.md +++ b/docs/ko/reference/promise/delay.md @@ -2,17 +2,19 @@ 코드의 실행을 주어진 밀리세컨드만큼 지연시켜요. -이 함수는 특정한 시간 이후에 Resolve되는 Promise를 반환해요. async/await 함수를 사용하는 경우에 함수의 실행을 잠깐 일시정지시킬 수 있어요. +이 함수는 특정한 시간 이후에 Resolve되는 Promise를 반환해요. async/await 함수를 사용하는 경우에 함수의 실행을 잠깐 일시정지시킬 수 있어요. 또한, 선택 옵션으로 지연을 취소할 수 있는 AbortSignal을 지원해요. ## 인터페이스 ```typescript -function delay(ms: number): Promise; +function delay(ms: number, options?: DelayOptions): Promise; ``` ### 파라미터 - `ms` (`number`): 코드 실행을 지연시킬 밀리세컨드. +- `options` (`DelayOptions`, optional): 옵션 객체. + - `signal` (`AbortSignal`, optional): 지연을 취소하기 위한 선택적 `AbortSignal`. ### 반환 값 @@ -20,6 +22,8 @@ function delay(ms: number): Promise; ## 예시 +### 기본 사용법 + ```typescript async function foo() { console.log('시작'); @@ -29,3 +33,19 @@ async function foo() { foo(); ``` + +### AbortSignal 사용법 + +```typescript +async function foo() { + const controller = new AbortController(); + const signal = controller.signal; + + setTimeout(() => controller.abort(), 50); // 50ms 후 지연을 취소 + try { + await delay(1000, { signal }); + } catch (error) { + console.log(error); // 'The operation was aborted' 로깅 + } +} +``` diff --git a/docs/reference/array/uniq.md b/docs/reference/array/uniq.md index 5cc2aac3c..85d06e4b0 100644 --- a/docs/reference/array/uniq.md +++ b/docs/reference/array/uniq.md @@ -1,5 +1,3 @@ -=== - # uniq Creates a duplicate-free version of an array. diff --git a/docs/reference/array/uniqBy.md b/docs/reference/array/uniqBy.md new file mode 100644 index 000000000..9bb8ac75f --- /dev/null +++ b/docs/reference/array/uniqBy.md @@ -0,0 +1,25 @@ +# uniqBy + +Returns a new array containing only the unique elements from the original array, based on the values returned by the `mapper` function. + +## Signature + +```typescript +function uniqBy(arr: T[], mapper: (item: T) => U): T[]; +``` + +### Parameters + +- `arr` (`T[]`): The array to process. +- `mapper` (`(item: T) => U`): The function used to convert the array elements. + +### Returns + +(`T[]`): A new array containing only the unique elements from the original array, based on the values returned by the `mapper` function. + +## Examples + +```typescript +uniqBy([1.2, 1.5, 2.1, 3.2, 5.7, 5.3, 7.19], Math.floor); +// [1.2, 2.1, 3.3, 5.7, 7.19] +``` diff --git a/docs/reference/array/uniqWith.md b/docs/reference/array/uniqWith.md new file mode 100644 index 000000000..0494647d7 --- /dev/null +++ b/docs/reference/array/uniqWith.md @@ -0,0 +1,25 @@ +# uniqWith + +Returns a new array containing only the unique elements from the original array, based on the values returned by the comparator function. + +## Signature + +```typescript +function uniqWith(arr: T[], areItemsEqual: (item1: T, item2: T) => boolean): T[]; +``` + +### Parameters + +- `arr` (`T[]`): The array to process. +- `areItemsEqual` (`(x: T, y: T) => boolean`): A custom function to determine if two elements are equal. This function takes two arguments, one from each array, and returns true if the elements are considered equal, and false otherwise. + +### Returns + +(`T[]`): A new array containing only the unique elements from the original array, based on the values returned by the comparator function. + +## Examples + +```typescript +uniqWith([1.2, 1.5, 2.1, 3.2, 5.7, 5.3, 7.19], (a, b) => Math.abs(a - b) < 1); +// [1.2, 3.2, 5.7, 7.19] +``` diff --git a/docs/reference/function/debounce.md b/docs/reference/function/debounce.md index 994e064ff..5a63d6591 100644 --- a/docs/reference/function/debounce.md +++ b/docs/reference/function/debounce.md @@ -7,13 +7,19 @@ method to cancel any pending execution. ## Signature ```typescript -function debounce void>(func: F, debounceMs: number): F & { cancel: () => void }; +function debounce void>( + func: F, + debounceMs: number, + options?: DebounceOptions +): F & { cancel: () => void }; ``` ### Parameters - `func` (`F`): The function to debounce. -- `debounceMs`(`number`): The number of milliseconds to delay. +- `debounceMs` (`number`): The number of milliseconds to delay. +- `options` (`DebounceOptions`, optional): An options object. + - `signal` (`AbortSignal`, optional): An optional `AbortSignal` to cancel the debounced function. ### Returns @@ -21,6 +27,8 @@ function debounce void>(func: F, debounceMs: numbe ## Examples +### Basic Usage + ```typescript const debouncedFunction = debounce(() => { console.log('Function executed'); @@ -32,3 +40,23 @@ debouncedFunction(); // Will not log anything as the previous call is canceled debouncedFunction.cancel(); ``` + +### Using with an AbortSignal + +```typescript +const controller = new AbortController(); +const signal = controller.signal; +const debouncedWithSignalFunction = debounce( + () => { + console.log('Function executed'); + }, + 1000, + { signal } +); + +// Will log 'Function executed' after 1 second if not called again in that time +debouncedWithSignalFunction(); + +// Will cancel the debounced function call +controller.abort(); +``` diff --git a/docs/reference/math/maxBy.md b/docs/reference/math/maxBy.md new file mode 100644 index 000000000..86563db98 --- /dev/null +++ b/docs/reference/math/maxBy.md @@ -0,0 +1,27 @@ +# maxBy + +Selects the first element of a list that has the maximum value of a function. + +If the list is empty, returns `undefined`. + +## Signature + +```typescript +function maxBy(elements: T[], selector: (element: T) => number): T; +``` + +### Parameters + +- `elements`: an array of elements to search through. +- `selector`: a function that takes an element and returns a number that the property of the object. + +### Returns + +The first element of the list that has the maximum value of the function. If the list is empty, returns `undefined`. + +### Example + +```typescript +maxBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // Returns: { a: 3 } +maxBy([], x => x.a); // Returns: undefined +``` diff --git a/docs/reference/math/minBy.md b/docs/reference/math/minBy.md new file mode 100644 index 000000000..bb209b4de --- /dev/null +++ b/docs/reference/math/minBy.md @@ -0,0 +1,27 @@ +# minBy + +Selects the first element of a list that has the minimum value of a function. + +If the list is empty, returns `undefined`. + +## Signature + +```typescript +function minBy(elements: T[], selector: (element: T) => number): T; +``` + +### Parameters + +- `elements`: an array of elements to search through. +- `selector`: a function that takes an element and returns a number that the property of the object. + +### Returns + +The first element of the list that has the minimum value of the function. If the list is empty, returns `undefined`. + +### Example + +```typescript +minBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // Returns: { a: 1 } +minBy([], x => x.a); // Returns: undefined +``` diff --git a/docs/reference/math/random.md b/docs/reference/math/random.md new file mode 100644 index 000000000..6eb4dd52c --- /dev/null +++ b/docs/reference/math/random.md @@ -0,0 +1,26 @@ +# random + +Generate a random number within the given range. The number can be an integer or a decimal. + +## Signature + +```typescript +function random(minimum: number, maximum: number): number; +``` + +### Parameters + +- `minimum` (`number`): The lower bound for the random number (inclusive). +- `maximum` (`number`): The upper bound for the random number (exclusive). + +### Returns + +- (`number`): A random number within the specified range. The number can be an integer or a decimal. + +## Examples + +```typescript +const result1 = random(0, 5); // Returns a random number between 0 and 5. +const result2 = random(5, 0); // If the minimum is greater than the maximum, an error is thrown +const result3 = random(5, 5); // If the minimum is equal to the maximum, an error is thrown. +``` diff --git a/docs/reference/math/randomInt.md b/docs/reference/math/randomInt.md new file mode 100644 index 000000000..d646a1d32 --- /dev/null +++ b/docs/reference/math/randomInt.md @@ -0,0 +1,26 @@ +# randomInt + +Generate a random integer within the given range. + +## Signature + +```typescript +function randomInt(minimum: number, maximum: number): number; +``` + +### Parameters + +- `minimum` (`number`): The lower bound for the random integer (inclusive). +- `maximum` (`number`): The upper bound for the random integer (exclusive). + +### Returns + +- (`number`): A random integer within the specified range. + +## Examples + +```typescript +const result1 = randomInt(0, 5); // Returns a random integer between 0 and 5. +const result2 = randomInt(5, 0); // If the minimum is greater than the maximum, an error is thrown +const result3 = randomInt(5, 5); // If the minimum is equal to the maximum, an error is thrown. +``` diff --git a/docs/reference/math/round.md b/docs/reference/math/round.md index 9b13fc607..5225b04c9 100644 --- a/docs/reference/math/round.md +++ b/docs/reference/math/round.md @@ -26,4 +26,5 @@ function round(value: number, precision?: number): number; const result1 = round(1.2345); // result1 will be 1 const result2 = round(1.2345, 2); // result2 will be 1.23 const result3 = round(1.2345, 3); // result3 will be 1.235 +const result4 = round(1.2345, 3.1); // This will throw an error ``` diff --git a/docs/reference/object/omitBy.md b/docs/reference/object/omitBy.md index 108439eb7..ff3ee0f32 100644 --- a/docs/reference/object/omitBy.md +++ b/docs/reference/object/omitBy.md @@ -8,13 +8,16 @@ includes only the properties for which the predicate function returns false. ## Signature ```typescript -function omitBy>(obj: T, shouldOmit: (value: any, key: string) => boolean): Partial; +function omitBy>( + obj: T, + shouldOmit: (value: T[keyof T], key: keyof T) => boolean +): Partial; ``` ### Parameters - `obj` (`T`): The object to omit properties from. -- `shouldOmit` (`(value: any, key: string) => boolean`): A predicate function that determines +- `shouldOmit` (`(value: T[keyof T], key: keyof T) => boolean`): A predicate function that determines whether a property should be omitted. It takes the property's key and value as arguments and returns `true` if the property should be omitted, and `false` otherwise. diff --git a/docs/reference/object/pickBy.md b/docs/reference/object/pickBy.md index 3510be96a..6bd528db2 100644 --- a/docs/reference/object/pickBy.md +++ b/docs/reference/object/pickBy.md @@ -10,14 +10,14 @@ includes only the properties for which the predicate function returns true. ```typescript function pickBy>( obj: T, - shouldPick: (value: T[keyof T], key: string) => boolean + shouldPick: (value: T[keyof T], key: keyof T) => boolean ): Partial; ``` ### Parameters - `obj` (`T`): The object to pick properties from. -- `shouldPick` (`(value: T[keyof T], key: string) => boolean`): A predicate function that determines whether a property should be picked. It takes the property's key and value as arguments and returns `true` if the property should be picked, and `false` otherwise. +- `shouldPick` (`(value: T[keyof T], key: keyof T) => boolean`): A predicate function that determines whether a property should be picked. It takes the property's key and value as arguments and returns `true` if the property should be picked, and `false` otherwise. ### Returns diff --git a/docs/reference/predicate/isUndefined.md b/docs/reference/predicate/isUndefined.md index ad8fcca82..0aa1c09f4 100644 --- a/docs/reference/predicate/isUndefined.md +++ b/docs/reference/predicate/isUndefined.md @@ -1,6 +1,6 @@ # isUndefined -Checks if the given value is undefined. +Checks if the given value is `undefined`. This function tests whether the provided value is strictly equal to `undefined`. It returns `true` if the value is `undefined`, and `false` otherwise. @@ -15,11 +15,11 @@ function isUndefined(x: unknown): x is undefined; ### Parameters -- `x` (`unknown`): The value to test if it is undefined. +- `x` (`unknown`): The value to test if it is `undefined`. ### Returns -(`x is undefined`): True if the value is undefined, false otherwise. +(`x is undefined`): `true` if the value is `undefined`, `false` otherwise. ## Examples diff --git a/docs/reference/promise/delay.md b/docs/reference/promise/delay.md index 8e92ed351..97f889db1 100644 --- a/docs/reference/promise/delay.md +++ b/docs/reference/promise/delay.md @@ -4,16 +4,19 @@ Delays the execution of code for a specified number of milliseconds. This function returns a Promise that resolves after the specified delay, allowing you to use it with async/await to pause execution. +It also supports an optional AbortSignal to cancel the delay. ## Signature ```typescript -function delay(ms: number): Promise; +function delay(ms: number, options?: DelayOptions): Promise; ``` ### Parameters - `ms` (`number`): The number of milliseconds to delay. +- `options` (`DelayOptions`, optional): An options object. + - `signal` (`AbortSignal`, optional): An optional `AbortSignal` to cancel the delay. ### Returns @@ -21,6 +24,8 @@ function delay(ms: number): Promise; ## Examples +### Basic Usage + ```typescript async function foo() { console.log('Start'); @@ -30,3 +35,19 @@ async function foo() { foo(); ``` + +### Using with an AbortSignal + +```typescript +async function foo() { + const controller = new AbortController(); + const signal = controller.signal; + + setTimeout(() => controller.abort(), 50); // Will cancel the delay after 50ms + try { + await delay(1000, { signal }); + } catch (error) { + console.log(error); // Will log 'The operation was aborted' + } +} +``` diff --git a/package.json b/package.json index 780971d1c..3901b2d61 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "es-toolkit", "description": "A state-of-the-art, high-performance JavaScript utility library with a small bundle size and strong type annotations.", - "version": "1.2.1", + "version": "1.4.0", "workspaces": [ "docs" ], @@ -105,6 +105,7 @@ "@babel/preset-typescript": "^7.24.1", "@changesets/changelog-github": "^0.5.0", "@changesets/cli": "^2.27.1", + "@codspeed/vitest-plugin": "^3.1.0", "@types/babel__core": "^7", "@types/babel__preset-env": "^7", "@types/broken-link-checker": "^0", @@ -127,7 +128,8 @@ "prepack": "yarn build", "build": "tsup && ./.scripts/postbuild.sh", "test": "vitest run --coverage --typecheck", + "bench": "vitest bench", "lint": "eslint ./src --ext .ts", "format": "prettier --write ." } -} \ No newline at end of file +} diff --git a/src/array/chunk.ts b/src/array/chunk.ts index ad729f17b..b4265f3df 100644 --- a/src/array/chunk.ts +++ b/src/array/chunk.ts @@ -21,7 +21,7 @@ * chunk(['a', 'b', 'c', 'd', 'e', 'f', 'g'], 3); * // Returns: [['a', 'b', 'c'], ['d', 'e', 'f'], ['g']] */ -export function chunk(arr: T[], size: number): T[][] { +export function chunk(arr: readonly T[], size: number): T[][] { if (!Number.isInteger(size) || size <= 0) { throw new Error('Size must be an integer greater than zero.'); } diff --git a/src/array/difference.ts b/src/array/difference.ts index b8d7a4247..4166992b1 100644 --- a/src/array/difference.ts +++ b/src/array/difference.ts @@ -20,7 +20,7 @@ * const result = difference(array1, array2); * // result will be [1, 3, 5] since 2 and 4 are in both arrays and are excluded from the result. */ -export function difference(firstArr: T[], secondArr: T[]): T[] { +export function difference(firstArr: readonly T[], secondArr: readonly T[]): T[] { const secondSet = new Set(secondArr); return firstArr.filter(item => !secondSet.has(item)); diff --git a/src/array/differenceBy.ts b/src/array/differenceBy.ts index 54853d1a0..74b156bb2 100644 --- a/src/array/differenceBy.ts +++ b/src/array/differenceBy.ts @@ -23,7 +23,7 @@ * const result = differenceBy(array1, array2, mapper); * // result will be [{ id: 1 }, { id: 3 }] since the elements with id 2 are in both arrays and are excluded from the result. */ -export function differenceBy(firstArr: T[], secondArr: T[], mapper: (value: T) => U): T[] { +export function differenceBy(firstArr: readonly T[], secondArr: readonly T[], mapper: (value: T) => U): T[] { const mappedSecondSet = new Set(secondArr.map(item => mapper(item))); return firstArr.filter(item => { diff --git a/src/array/differenceWith.ts b/src/array/differenceWith.ts index ed1fc9233..7978338b3 100644 --- a/src/array/differenceWith.ts +++ b/src/array/differenceWith.ts @@ -19,7 +19,11 @@ * const result = differenceWith(array1, array2, areItemsEqual); * // result will be [{ id: 1 }, { id: 3 }] since the elements with id 2 are considered equal and are excluded from the result. */ -export function differenceWith(firstArr: T[], secondArr: T[], areItemsEqual: (x: T, y: T) => boolean): T[] { +export function differenceWith( + firstArr: readonly T[], + secondArr: readonly T[], + areItemsEqual: (x: T, y: T) => boolean +): T[] { return firstArr.filter(firstItem => { return secondArr.every(secondItem => { return !areItemsEqual(firstItem, secondItem); diff --git a/src/array/drop.ts b/src/array/drop.ts index 9526c03e2..707f9fc72 100644 --- a/src/array/drop.ts +++ b/src/array/drop.ts @@ -13,6 +13,6 @@ * const result = drop(array, 2); * // result will be [3, 4, 5] since the first two elements are dropped. */ -export function drop(arr: T[], itemsCount: number): T[] { +export function drop(arr: readonly T[], itemsCount: number): T[] { return arr.slice(itemsCount); } diff --git a/src/array/dropRight.ts b/src/array/dropRight.ts index 03e51a04e..b23ca34f8 100644 --- a/src/array/dropRight.ts +++ b/src/array/dropRight.ts @@ -13,6 +13,6 @@ * const result = dropRight(array, 2); * // result will be [1, 2, 3] since the last two elements are dropped. */ -export function dropRight(arr: T[], itemsCount: number): T[] { +export function dropRight(arr: readonly T[], itemsCount: number): T[] { return arr.slice(0, -itemsCount); } diff --git a/src/array/dropRightWhile.spec.ts b/src/array/dropRightWhile.spec.ts index 6c1aa69af..bead5cb6e 100644 --- a/src/array/dropRightWhile.spec.ts +++ b/src/array/dropRightWhile.spec.ts @@ -21,5 +21,7 @@ describe('dropRightWhile', () => { enabled: true, }, ]); + + expect(dropRightWhile([1, 2, 3], x => x < 4)).toEqual([]); }); }); diff --git a/src/array/dropRightWhile.ts b/src/array/dropRightWhile.ts index 4b371cea8..acc6ed07e 100644 --- a/src/array/dropRightWhile.ts +++ b/src/array/dropRightWhile.ts @@ -17,7 +17,7 @@ import { dropWhile } from './dropWhile'; * const result = dropRightWhile(array, x => x > 3); * // result will be [1, 2, 3] since elements greater than 3 are dropped from the end. */ -export function dropRightWhile(arr: T[], canContinueDropping: (item: T) => boolean): T[] { +export function dropRightWhile(arr: readonly T[], canContinueDropping: (item: T) => boolean): T[] { const reversed = arr.slice().reverse(); const dropped = dropWhile(reversed, canContinueDropping); return dropped.slice().reverse(); diff --git a/src/array/dropWhile.spec.ts b/src/array/dropWhile.spec.ts index b29f3e0cd..ee654ccbb 100644 --- a/src/array/dropWhile.spec.ts +++ b/src/array/dropWhile.spec.ts @@ -18,5 +18,7 @@ describe('dropWhile', () => { }, { id: 3, enabled: false }, ]); + + expect(dropWhile([1, 2, 3], x => x < 4)).toEqual([]); }); }); diff --git a/src/array/dropWhile.ts b/src/array/dropWhile.ts index 88bd19840..8713b27c3 100644 --- a/src/array/dropWhile.ts +++ b/src/array/dropWhile.ts @@ -15,7 +15,11 @@ * const result = dropWhile(array, x => x < 3); * // result will be [3, 4, 5] since elements less than 3 are dropped. */ -export function dropWhile(arr: T[], canContinueDropping: (item: T) => boolean): T[] { +export function dropWhile(arr: readonly T[], canContinueDropping: (item: T) => boolean): T[] { const dropEndIndex = arr.findIndex(item => !canContinueDropping(item)); + if (dropEndIndex === -1) { + return []; + } + return arr.slice(dropEndIndex); } diff --git a/src/array/groupBy.ts b/src/array/groupBy.ts index c9292165f..f39572b21 100644 --- a/src/array/groupBy.ts +++ b/src/array/groupBy.ts @@ -28,7 +28,7 @@ * // ] * // } */ -export function groupBy(arr: T[], getKeyFromItem: (item: T) => K): Record { +export function groupBy(arr: readonly T[], getKeyFromItem: (item: T) => K): Record { const result = {} as Record; for (const item of arr) { diff --git a/src/array/intersection.ts b/src/array/intersection.ts index cd3bc527c..2ebaf8d62 100644 --- a/src/array/intersection.ts +++ b/src/array/intersection.ts @@ -15,8 +15,10 @@ * const result = intersection(array1, array2); * // result will be [3, 4, 5] since these elements are in both arrays. */ -export function intersection(firstArr: T[], secondArr: T[]): T[] { +export function intersection(firstArr: readonly T[], secondArr: readonly T[]): T[] { + const secondSet = new Set(secondArr); + return firstArr.filter(item => { - return secondArr.includes(item); + return secondSet.has(item); }); } diff --git a/src/array/intersectionBy.ts b/src/array/intersectionBy.ts index 4bd62fe06..c0e9a4a12 100644 --- a/src/array/intersectionBy.ts +++ b/src/array/intersectionBy.ts @@ -18,10 +18,7 @@ * const result = intersectionBy(array1, array2, mapper); * // result will be [{ id: 2 }] since only this element has a matching id in both arrays. */ -export function intersectionBy(firstArr: T[], secondArr: T[], mapper: (item: T) => U): T[] { - const mappedSecondArr = secondArr.map(x => mapper(x)); - - return firstArr.filter(item => { - return mappedSecondArr.includes(mapper(item)); - }); +export function intersectionBy(firstArr: readonly T[], secondArr: readonly T[], mapper: (item: T) => U): T[] { + const mappedSecondSet = new Set(secondArr.map(mapper)); + return firstArr.filter(item => mappedSecondSet.has(mapper(item))); } diff --git a/src/array/intersectionWith.ts b/src/array/intersectionWith.ts index 4190c0987..5e397286b 100644 --- a/src/array/intersectionWith.ts +++ b/src/array/intersectionWith.ts @@ -19,7 +19,11 @@ * const result = intersectionWith(array1, array2, areItemsEqual); * // result will be [{ id: 2 }] since this element has a matching id in both arrays. */ -export function intersectionWith(firstArr: T[], secondArr: T[], areItemsEqual: (x: T, y: T) => boolean): T[] { +export function intersectionWith( + firstArr: readonly T[], + secondArr: readonly T[], + areItemsEqual: (x: T, y: T) => boolean +): T[] { return firstArr.filter(firstItem => { return secondArr.some(secondItem => { return areItemsEqual(firstItem, secondItem); diff --git a/src/array/partition.spec.ts b/src/array/partition.spec.ts index a4dc0919e..13c6b96b5 100644 --- a/src/array/partition.spec.ts +++ b/src/array/partition.spec.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, expectTypeOf } from 'vitest'; import { partition } from './partition'; describe('partition', () => { @@ -25,4 +25,15 @@ describe('partition', () => { ], ]); }); + + it('should correctly infer the type of a narrow array', () => { + const arr = [1, 2, 3, 4, 5] as const; + const [evens, odds] = partition(arr, num => num % 2 === 0); + + expect(evens).toEqual([2, 4]); + expect(odds).toEqual([1, 3, 5]); + + expectTypeOf(evens).toEqualTypeOf>(); + expectTypeOf(odds).toEqualTypeOf>(); + }); }); diff --git a/src/array/partition.ts b/src/array/partition.ts index 548e7fa64..562361b97 100644 --- a/src/array/partition.ts +++ b/src/array/partition.ts @@ -19,7 +19,7 @@ * const [even, odd] = partition(array, isEven); * // even will be [2, 4], and odd will be [1, 3, 5] */ -export function partition(arr: T[], isInTruthy: (value: T) => boolean): [truthy: T[], falsy: T[]] { +export function partition(arr: readonly T[], isInTruthy: (value: T) => boolean): [truthy: T[], falsy: T[]] { const truthy: T[] = []; const falsy: T[] = []; diff --git a/src/array/sample.ts b/src/array/sample.ts index 5e81f9ddb..3cc38f6c2 100644 --- a/src/array/sample.ts +++ b/src/array/sample.ts @@ -11,7 +11,7 @@ * const randomElement = sample(array); * // randomElement will be one of the elements from the array, selected randomly. */ -export function sample(arr: T[]): T { +export function sample(arr: readonly T[]): T { const randomIndex = Math.floor(Math.random() * arr.length); return arr[randomIndex]; } diff --git a/src/array/shuffle.ts b/src/array/shuffle.ts index 743833204..41433c915 100644 --- a/src/array/shuffle.ts +++ b/src/array/shuffle.ts @@ -11,7 +11,7 @@ * const shuffledArray = shuffle(array); * // shuffledArray will be a new array with elements of array in random order, e.g., [3, 1, 4, 5, 2] */ -export function shuffle(arr: T[]): T[] { +export function shuffle(arr: readonly T[]): T[] { const result = arr.slice(); /** diff --git a/src/array/take.spec.ts b/src/array/take.spec.ts index 6751bb125..d738d6e2f 100644 --- a/src/array/take.spec.ts +++ b/src/array/take.spec.ts @@ -6,9 +6,6 @@ describe('take', () => { expect(take([1, 2, 3, 4, 5], 3)).toEqual([1, 2, 3]); expect(take(['a', 'b', 'c', 'd'], 2)).toEqual(['a', 'b']); expect(take([true, false, true], 1)).toEqual([true]); - expect(take([1, 2, 3], 5)).toEqual([1, 2, 3]); - expect(take([1, 2, 3], 0)).toEqual([]); - expect(take([], 3)).toEqual([]); }); it('handles cases where count is greater than array length', () => { diff --git a/src/array/take.ts b/src/array/take.ts index 1b5748c2c..5b3dc2581 100644 --- a/src/array/take.ts +++ b/src/array/take.ts @@ -20,6 +20,6 @@ * // Returns [1, 2, 3] * take([1, 2, 3], 5); */ -export function take(arr: T[], count: number): T[] { +export function take(arr: readonly T[], count: number): T[] { return arr.slice(0, count); } diff --git a/src/array/takeRight.ts b/src/array/takeRight.ts index a1dae50f1..2009227d2 100644 --- a/src/array/takeRight.ts +++ b/src/array/takeRight.ts @@ -18,7 +18,7 @@ * // Returns [1, 2, 3] * takeRight([1, 2, 3], 5); */ -export function takeRight(arr: T[], count: number): T[] { +export function takeRight(arr: readonly T[], count: number): T[] { if (count === 0) { return []; } diff --git a/src/array/takeRightWhile.ts b/src/array/takeRightWhile.ts index 6c0e46836..727307389 100644 --- a/src/array/takeRightWhile.ts +++ b/src/array/takeRightWhile.ts @@ -15,7 +15,7 @@ * // Returns [] * takeRightWhile([1, 2, 3], n => n > 3); */ -export function takeRightWhile(arr: T[], shouldContinueTaking: (item: T) => boolean): T[] { +export function takeRightWhile(arr: readonly T[], shouldContinueTaking: (item: T) => boolean): T[] { for (let i = arr.length - 1; i >= 0; i--) { if (!shouldContinueTaking(arr[i])) { return arr.slice(i + 1); diff --git a/src/array/takeWhile.ts b/src/array/takeWhile.ts index 794f75485..c445feb2c 100644 --- a/src/array/takeWhile.ts +++ b/src/array/takeWhile.ts @@ -16,7 +16,7 @@ * // Returns [] * takeWhile([1, 2, 3, 4], x => x > 3); */ -export function takeWhile(arr: T[], shouldContinueTaking: (element: T) => boolean): T[] { +export function takeWhile(arr: readonly T[], shouldContinueTaking: (element: T) => boolean): T[] { const result: T[] = []; for (const item of arr) { diff --git a/src/array/union.ts b/src/array/union.ts index 0129eba21..ad213f919 100644 --- a/src/array/union.ts +++ b/src/array/union.ts @@ -16,6 +16,6 @@ import { uniq } from './uniq'; * const result = union(array1, array2); * // result will be [1, 2, 3, 4, 5] */ -export function union(arr1: T[], arr2: T[]): T[] { +export function union(arr1: readonly T[], arr2: readonly T[]): T[] { return uniq(arr1.concat(arr2)); } diff --git a/src/array/unionBy.spec.ts b/src/array/unionBy.spec.ts index a9a6fe182..95a5c8d65 100644 --- a/src/array/unionBy.spec.ts +++ b/src/array/unionBy.spec.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'; import { unionBy } from './unionBy'; describe('unionBy', () => { - it('should work with a `converter`', () => { + it('should work with a `mapper`', () => { expect(unionBy([2.1], [1.2, 2.3], Math.floor)).toEqual([2.1, 1.2]); expect(unionBy([{ x: 1 }], [{ x: 2 }, { x: 1 }], ({ x }) => x)).toEqual([{ x: 1 }, { x: 2 }]); }); diff --git a/src/array/unionBy.ts b/src/array/unionBy.ts index 48e876866..3acbbd5ef 100644 --- a/src/array/unionBy.ts +++ b/src/array/unionBy.ts @@ -18,15 +18,16 @@ * unionBy([{ id: 1 }, { id: 2 }], [{ id: 2 }, { id: 3 }], idMapper); * // Returns [{ id: 1 }, { id: 2 }, { id: 3 }] */ -export function unionBy(arr1: T[], arr2: T[], mapper: (item: T) => U): T[] { +export function unionBy(arr1: readonly T[], arr2: readonly T[], mapper: (item: T) => U): T[] { const map = new Map(); for (const item of [...arr1, ...arr2]) { const key = mapper(item); + if (!map.has(key)) { map.set(key, item); } } - return [...map.values()]; + return Array.from(map.values()); } diff --git a/src/array/unionWith.ts b/src/array/unionWith.ts index 85c5bb468..fedad6a97 100644 --- a/src/array/unionWith.ts +++ b/src/array/unionWith.ts @@ -19,6 +19,10 @@ import { uniqWith } from './uniqWith'; * const result = unionWith(array1, array2, areItemsEqual); * // result will be [{ id: 1 }, { id: 2 }, { id: 3 }] since { id: 2 } is considered equal in both arrays */ -export function unionWith(arr1: T[], arr2: T[], areItemsEqual: (item1: T, item2: T) => boolean): T[] { +export function unionWith( + arr1: readonly T[], + arr2: readonly T[], + areItemsEqual: (item1: T, item2: T) => boolean +): T[] { return uniqWith(arr1.concat(arr2), areItemsEqual); } diff --git a/src/array/uniq.spec.ts b/src/array/uniq.spec.ts index 3ce86b968..dc013fec1 100644 --- a/src/array/uniq.spec.ts +++ b/src/array/uniq.spec.ts @@ -5,4 +5,35 @@ describe('uniq', () => { it('uniq function creates unique elements from the array passed as an argument.', () => { expect(uniq([11, 2, 3, 44, 11, 2, 3])).toEqual([11, 2, 3, 44]); }); + it('uniq function works with strings.', () => { + expect(uniq(['a', 'b', 'b', 'c', 'a'])).toEqual(['a', 'b', 'c']); + }); + it('uniq function works with boolean values.', () => { + expect(uniq([true, false, true, false, false])).toEqual([true, false]); + }); + it('uniq function works with nullish values.', () => { + expect(uniq([null, undefined, null, undefined])).toEqual([null, undefined]); + }); + it('uniq function works with empty arrays.', () => { + expect(uniq([])).toEqual([]); + }); + it('uniq function works with multiple types.', () => { + expect(uniq([1, 'a', 2, 'b', 1, 'a'])).toEqual([1, 'a', 2, 'b']); + }); + it('uniq function keeps its original order.', () => { + expect(uniq([1, 2, 2, 3, 4, 4, 5])).toEqual([1, 2, 3, 4, 5]); + }); + it('uniq function should create a new array.', () => { + const array = [1, 2, 3]; + const result = uniq(array); + + expect(result).toEqual([1, 2, 3]); + expect(result).not.toBe(array); + }); + it('uniq function should not mutate the original array.', () => { + const array = [1, 2, 3, 2, 1, 3]; + uniq(array); + + expect(array).toEqual([1, 2, 3, 2, 1, 3]); + }); }); diff --git a/src/array/uniq.ts b/src/array/uniq.ts index d39a1580b..d6c60b230 100644 --- a/src/array/uniq.ts +++ b/src/array/uniq.ts @@ -12,16 +12,6 @@ * const result = uniq(array); * // result will be [1, 2, 3, 4, 5] */ -export function uniq(arr: T[]): T[] { - const result: T[] = []; - - for (const item of arr) { - if (result.includes(item)) { - continue; - } - - result.push(item); - } - - return result; +export function uniq(arr: readonly T[]): T[] { + return Array.from(new Set(arr)); } diff --git a/src/array/uniqBy.spec.ts b/src/array/uniqBy.spec.ts index 49bee324c..a2821b9a7 100644 --- a/src/array/uniqBy.spec.ts +++ b/src/array/uniqBy.spec.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'; import { uniqBy } from './uniqBy'; describe('uniqBy', () => { - it('should work with a `converter`', () => { + it('should work with a `mapper`', () => { expect(uniqBy([2.1, 1.2, 2.3], Math.floor)).toEqual([2.1, 1.2]); }); }); diff --git a/src/array/uniqBy.ts b/src/array/uniqBy.ts index 6b7f61b63..6da279611 100644 --- a/src/array/uniqBy.ts +++ b/src/array/uniqBy.ts @@ -1,13 +1,10 @@ -import { uniqWith } from './uniqWith'; - /** - * The `uniqBy` function takes an array as its first argument and a 'converter' function as the second. It maps the array elements using the converter function, then removes any duplicates. - * - * It filters out elements with the same value, meaning it does not check for duplicates in data types like Objects. + * Returns a new array containing only the unique elements from the original array, + * based on the values returned by the mapper function. * * @param {T[]} arr - The array to process. - * @param {(item: T) => U} converter - The function used to convert the array elements. - * @returns {T[]} A new array containing only the unique elements from the original array, based on the values returned by the converter function. + * @param {(item: T) => U} mapper - The function used to convert the array elements. + * @returns {T[]} A new array containing only the unique elements from the original array, based on the values returned by the mapper function. * * @example * ```ts @@ -15,6 +12,16 @@ import { uniqWith } from './uniqWith'; * // [1.2, 2.1, 3.3, 5.7, 7.19] * ``` */ -export function uniqBy(arr: T[], converter: (item: T) => U): T[] { - return uniqWith(arr, (item1, item2) => converter(item1) === converter(item2)); +export function uniqBy(arr: readonly T[], mapper: (item: T) => U): T[] { + const map = new Map(); + + for (const item of arr) { + const key = mapper(item); + + if (!map.has(key)) { + map.set(key, item); + } + } + + return Array.from(map.values()); } diff --git a/src/array/uniqWith.ts b/src/array/uniqWith.ts index 531f3a70a..6c467a83b 100644 --- a/src/array/uniqWith.ts +++ b/src/array/uniqWith.ts @@ -1,7 +1,6 @@ /** - * The `uniqWith` function takes an array as its first argument and a 'comparator' function as the second. - * - * It evaluates the elements of the array using the comparator function, and if true is returned, it considers those elements as duplicates and removes them. + * Returns a new array containing only the unique elements from the original array, + * based on the values returned by the comparator function. * * @param {T[]} arr - The array to process. * @param {(item1: T, item2: T) => boolean} areItemsEqual - The function used to compare the array elements. @@ -13,7 +12,7 @@ * // [1.2, 3.2, 5.7, 7.19] * ``` */ -export function uniqWith(arr: T[], areItemsEqual: (item1: T, item2: T) => boolean): T[] { +export function uniqWith(arr: readonly T[], areItemsEqual: (item1: T, item2: T) => boolean): T[] { const result: T[] = []; for (const item of arr) { diff --git a/src/array/xor.ts b/src/array/xor.ts index 93c264e3d..82454f2ff 100644 --- a/src/array/xor.ts +++ b/src/array/xor.ts @@ -18,6 +18,6 @@ import { union } from './union'; * // Returns ['a', 'c'] * xor(['a', 'b'], ['b', 'c']); */ -export function xor(arr1: T[], arr2: T[]): T[] { +export function xor(arr1: readonly T[], arr2: readonly T[]): T[] { return difference(union(arr1, arr2), intersection(arr1, arr2)); } diff --git a/src/array/xorBy.ts b/src/array/xorBy.ts index 45533c042..2d44a3f37 100644 --- a/src/array/xorBy.ts +++ b/src/array/xorBy.ts @@ -21,7 +21,7 @@ import { unionBy } from './unionBy'; * xorBy([{ id: 1 }, { id: 2 }], [{ id: 2 }, { id: 3 }], idMapper); * // Returns [{ id: 1 }, { id: 3 }] */ -export function xorBy(arr1: T[], arr2: T[], mapper: (item: T) => U): T[] { +export function xorBy(arr1: readonly T[], arr2: readonly T[], mapper: (item: T) => U): T[] { const union = unionBy(arr1, arr2, mapper); const intersection = intersectionBy(arr1, arr2, mapper); diff --git a/src/array/xorWith.ts b/src/array/xorWith.ts index 7d3298c34..3d5053bff 100644 --- a/src/array/xorWith.ts +++ b/src/array/xorWith.ts @@ -20,7 +20,11 @@ import { unionWith } from './unionWith'; * xorWith([{ id: 1 }, { id: 2 }], [{ id: 2 }, { id: 3 }], areObjectsEqual); * // Returns [{ id: 1 }, { id: 3 }] */ -export function xorWith(arr1: T[], arr2: T[], areElementsEqual: (item1: T, item2: T) => boolean): T[] { +export function xorWith( + arr1: readonly T[], + arr2: readonly T[], + areElementsEqual: (item1: T, item2: T) => boolean +): T[] { const union = unionWith(arr1, arr2, areElementsEqual); const intersection = intersectionWith(arr1, arr2, areElementsEqual); diff --git a/src/array/zip.ts b/src/array/zip.ts index 429130d56..6555dbb24 100644 --- a/src/array/zip.ts +++ b/src/array/zip.ts @@ -19,11 +19,16 @@ * const result2 = zip(arr1, arr2, arr3); * // result2 will be [[1, 'a', true], [2, 'b', false], [3, 'c', undefined]] */ -export function zip(arr1: T[]): Array<[T]>; -export function zip(arr1: T[], arr2: U[]): Array<[T, U]>; -export function zip(arr1: T[], arr2: U[], arr3: V[]): Array<[T, U, V]>; -export function zip(arr1: T[], arr2: U[], arr3: V[], arr4: W[]): Array<[T, U, V, W]>; -export function zip(...arrs: T[][]): T[][] { +export function zip(arr1: readonly T[]): Array<[T]>; +export function zip(arr1: readonly T[], arr2: readonly U[]): Array<[T, U]>; +export function zip(arr1: readonly T[], arr2: readonly U[], arr3: readonly V[]): Array<[T, U, V]>; +export function zip( + arr1: readonly T[], + arr2: readonly U[], + arr3: readonly V[], + arr4: readonly W[] +): Array<[T, U, V, W]>; +export function zip(...arrs: Array): T[][] { const result: T[][] = []; const maxIndex = Math.max(...arrs.map(x => x.length)); diff --git a/src/array/zipWith.ts b/src/array/zipWith.ts index 04d318858..bdf0ab8f6 100644 --- a/src/array/zipWith.ts +++ b/src/array/zipWith.ts @@ -26,17 +26,22 @@ * const result = zipWith(arr1, arr2, arr3, (a, b, c) => `${a}${b}${c}`); * // result will be [`135`, `246`] */ -export function zipWith(arr1: T[], combine: (item: T) => R): R[]; -export function zipWith(arr1: T[], arr2: U[], combine: (item1: T, item2: U) => R): R[]; -export function zipWith(arr1: T[], arr2: U[], arr3: V[], combine: (item1: T, item2: U, item3: V) => R): R[]; +export function zipWith(arr1: readonly T[], combine: (item: T) => R): R[]; +export function zipWith(arr1: readonly T[], arr2: readonly U[], combine: (item1: T, item2: U) => R): R[]; +export function zipWith( + arr1: readonly T[], + arr2: readonly U[], + arr3: readonly V[], + combine: (item1: T, item2: U, item3: V) => R +): R[]; export function zipWith( - arr1: T[], - arr2: U[], - arr3: V[], - arr4: W[], + arr1: readonly T[], + arr2: readonly U[], + arr3: readonly V[], + arr4: readonly W[], combine: (item1: T, item2: U, item3: V, item4: W) => R ): R[]; -export function zipWith(arr1: T[], ...rest: any[]): R[] { +export function zipWith(arr1: readonly T[], ...rest: any[]): R[] { const arrs = [arr1, ...rest.slice(0, -1)]; const combine = rest[rest.length - 1] as (...items: T[]) => R; diff --git a/src/error/AbortError.ts b/src/error/AbortError.ts new file mode 100644 index 000000000..aeb96a8e6 --- /dev/null +++ b/src/error/AbortError.ts @@ -0,0 +1,6 @@ +export class AbortError extends Error { + constructor(message = 'The operation was aborted') { + super(message); + this.name = 'AbortError'; + } +} diff --git a/src/error/index.ts b/src/error/index.ts new file mode 100644 index 000000000..1b24ff2d2 --- /dev/null +++ b/src/error/index.ts @@ -0,0 +1 @@ +export { AbortError } from './AbortError'; diff --git a/src/function/debounce.spec.ts b/src/function/debounce.spec.ts index c9087f20a..ab4d1f7c0 100644 --- a/src/function/debounce.spec.ts +++ b/src/function/debounce.spec.ts @@ -94,4 +94,59 @@ describe('debounce', () => { expect(func).toHaveBeenCalledTimes(1); expect(func).toHaveBeenCalledWith('test', 123); }); + + it('should cancel the debounced function call if aborted via AbortSignal', async () => { + const func = vi.fn(); + const debounceMs = 50; + const controller = new AbortController(); + const signal = controller.signal; + const debouncedFunc = debounce(func, debounceMs, { signal }); + + debouncedFunc(); + controller.abort(); + + await delay(debounceMs); + + expect(func).not.toHaveBeenCalled(); + }); + + it('should not call the debounced function if it is already aborted by AbortSignal', async () => { + const controller = new AbortController(); + const signal = controller.signal; + + controller.abort(); + + const func = vi.fn(); + + const debounceMs = 50; + const debouncedFunc = debounce(func, debounceMs, { signal }); + + debouncedFunc(); + + await delay(debounceMs); + + expect(func).not.toHaveBeenCalled(); + }); + + it('should not add multiple abort event listeners', async () => { + const func = vi.fn(); + const debounceMs = 100; + const controller = new AbortController(); + const signal = controller.signal; + const addEventListenerSpy = vi.spyOn(signal, 'addEventListener'); + + const debouncedFunc = debounce(func, debounceMs, { signal }); + + debouncedFunc(); + debouncedFunc(); + + await new Promise(resolve => setTimeout(resolve, 150)); + + expect(func).toHaveBeenCalledTimes(1); + + const listenerCount = addEventListenerSpy.mock.calls.filter(([event]) => event === 'abort').length; + expect(listenerCount).toBe(1); + + addEventListenerSpy.mockRestore(); + }); }); diff --git a/src/function/debounce.ts b/src/function/debounce.ts index e346b28c0..f761e95fd 100644 --- a/src/function/debounce.ts +++ b/src/function/debounce.ts @@ -1,3 +1,7 @@ +interface DebounceOptions { + signal?: AbortSignal; +} + /** * Creates a debounced function that delays invoking the provided function until after `debounceMs` milliseconds * have elapsed since the last time the debounced function was invoked. The debounced function also has a `cancel` @@ -5,6 +9,8 @@ * * @param {F} func - The function to debounce. * @param {number} debounceMs - The number of milliseconds to delay. + * @param {DebounceOptions} options - The options object. + * @param {AbortSignal} options.signal - An optional AbortSignal to cancel the debounced function. * @returns {F & { cancel: () => void }} A new debounced function with a `cancel` method. * * @example @@ -17,25 +23,53 @@ * * // Will not log anything as the previous call is canceled * debouncedFunction.cancel(); + * + * // With AbortSignal + * const controller = new AbortController(); + * const signal = controller.signal; + * const debouncedWithSignal = debounce(() => { + * console.log('Function executed'); + * }, 1000, { signal }); + * + * debouncedWithSignal(); + * + * // Will cancel the debounced function call + * controller.abort(); */ -export function debounce void>(func: F, debounceMs: number): F & { cancel: () => void } { +export function debounce void>( + func: F, + debounceMs: number, + { signal }: DebounceOptions = {} +): F & { cancel: () => void } { let timeoutId: number | NodeJS.Timeout | null = null; const debounced = function (...args: Parameters) { - if (timeoutId != null) { + if (timeoutId !== null) { clearTimeout(timeoutId); } + if (signal?.aborted) { + return; + } + timeoutId = setTimeout(() => { func(...args); + timeoutId = null; }, debounceMs); } as F & { cancel: () => void }; + const onAbort = function () { + debounced.cancel(); + }; + debounced.cancel = function () { - if (timeoutId != null) { + if (timeoutId !== null) { clearTimeout(timeoutId); + timeoutId = null; } }; + signal?.addEventListener('abort', onAbort, { once: true }); + return debounced; } diff --git a/src/index.ts b/src/index.ts index 888dd3fad..3a6c6d268 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export * from './array'; +export * from './error'; export * from './function'; export * from './math'; export * from './object'; diff --git a/src/math/index.ts b/src/math/index.ts index 893e9d773..41c2ba372 100644 --- a/src/math/index.ts +++ b/src/math/index.ts @@ -1,3 +1,7 @@ export { clamp } from './clamp'; +export { random } from './random'; +export { randomInt } from './randomInt'; export { round } from './round'; export { sum } from './sum'; +export { maxBy } from './maxBy'; +export { minBy } from './minBy'; diff --git a/src/math/maxBy.spec.ts b/src/math/maxBy.spec.ts new file mode 100644 index 000000000..a06a52e17 --- /dev/null +++ b/src/math/maxBy.spec.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest'; +import { maxBy } from './maxBy'; + +describe('maxBy', () => { + it('maxBy selects one max value in array', () => { + const people = [ + { name: 'Mark', age: 25 }, + { name: 'Nunu', age: 30 }, + { name: 'Overmars', age: 20 }, + ]; + const result = maxBy(people, person => person.age); + expect(result).toEqual({ name: 'Nunu', age: 30 }); + }); + + it('if there are two max values, first one is selected', () => { + const people = [ + { name: 'Mark', age: 25 }, + { name: 'Nunu', age: 30 }, + { name: 'Overmars', age: 30 }, + ]; + const result = maxBy(people, person => person.age); + expect(result).toEqual({ name: 'Nunu', age: 30 }); + }); + + it('if array is single-element, return unique element of array', () => { + const people = [{ name: 'Mark', age: 25 }]; + const result = maxBy(people, person => person.age); + expect(result).toEqual({ name: 'Mark', age: 25 }); + }); + + it('if array is empty, return undefined', () => { + type Person = { name: string; age: number }; + const people: Person[] = []; + const result = maxBy(people, person => person.age); + expect(result).toBeUndefined(); + }); +}); diff --git a/src/math/maxBy.ts b/src/math/maxBy.ts new file mode 100644 index 000000000..2618c9a13 --- /dev/null +++ b/src/math/maxBy.ts @@ -0,0 +1,32 @@ +/** + * Returns the element of the specified array that has the maximum value, according to the specified selector. + * + * This function takes an array of elements and a selector function, and returns the element with the maximum value according to the selector. + * + * @param {T[]} elements - An array of elements to be compared. + * @param {(element: T) => number} selector - A function that takes an element and returns a number. + * @returns {T} The element with the maximum value according to the selector. + * + * @example + * const people = [ + * { name: 'Mark', age: 25 }, + * { name: 'Nunu', age: 30 }, + * { name: 'Overmars', age: 20 } + * ]; + * const result = maxBy(people, person => person.age); + * // result will be { name: 'Nunu', age: 30 } + */ +export function maxBy(elements: T[], selector: (element: T) => number): T { + let maxElement = elements[0]; + let max = -Infinity; + + for (const element of elements) { + const value = selector(element); + if (value > max) { + max = value; + maxElement = element; + } + } + + return maxElement; +} diff --git a/src/math/minBy.spec.ts b/src/math/minBy.spec.ts new file mode 100644 index 000000000..eba9c55a4 --- /dev/null +++ b/src/math/minBy.spec.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest'; +import { minBy } from './minBy'; + +describe('minBy', () => { + it('minBy selects one max value in array', () => { + const people = [ + { name: 'Mark', age: 30 }, + { name: 'Nunu', age: 20 }, + { name: 'Overmars', age: 35 }, + ]; + const result = minBy(people, person => person.age); + expect(result).toEqual({ name: 'Nunu', age: 20 }); + }); + + it('if there are two max values, first one is selected', () => { + const people = [ + { name: 'Mark', age: 30 }, + { name: 'Nunu', age: 20 }, + { name: 'Overmars', age: 20 }, + ]; + const result = minBy(people, person => person.age); + expect(result).toEqual({ name: 'Nunu', age: 20 }); + }); + + it('if array is single-element, return unique element of array', () => { + const people = [{ name: 'Mark', age: 25 }]; + const result = minBy(people, person => person.age); + expect(result).toEqual({ name: 'Mark', age: 25 }); + }); + + it('if array is empty, return undefined', () => { + type Person = { name: string; age: number }; + const people: Person[] = []; + const result = minBy(people, person => person.age); + expect(result).toBeUndefined(); + }); +}); diff --git a/src/math/minBy.ts b/src/math/minBy.ts new file mode 100644 index 000000000..ce2aba8cb --- /dev/null +++ b/src/math/minBy.ts @@ -0,0 +1,24 @@ +/** + * Returns the minimum value in an array of numbers. + * + * This function takes an array of elements and a selector function, and returns the element with the minimum value according to the selector. + * + * If the array is empty, the function returns `undefined`. + * + * @param elements An array of elements. + * @param selector A function that selects a number from an element. + */ +export function minBy(elements: T[], selector: (element: T) => number): T { + let minElement = elements[0]; + let min = Infinity; + + for (const element of elements) { + const value = selector(element); + if (value < min) { + min = value; + minElement = element; + } + } + + return minElement; +} diff --git a/src/math/random.spec.ts b/src/math/random.spec.ts new file mode 100644 index 000000000..4df3224bb --- /dev/null +++ b/src/math/random.spec.ts @@ -0,0 +1,46 @@ +import { describe, expect, it } from 'vitest'; +import { random } from './random'; + +describe('random', () => { + it('generates a random floating-point number between min (inclusive) and max (exclusive)', () => { + const min = 0; + const max = 5; + for (let i = 0; i < 100; i++) { + const result = random(min, max); + expect(result).toBeGreaterThanOrEqual(min); + expect(result).toBeLessThan(max); + } + }); + + it('throws an error if min is greater than max', () => { + const min = 5; + const max = 0; + expect(() => random(min, max)).toThrow('Invalid input: The maximum value must be greater than the minimum value.'); + }); + + it('handles edge cases where min and max are the same', () => { + const min = 5; + const max = 5; + expect(() => random(min, max)).toThrow('Invalid input: The maximum value must be greater than the minimum value.'); + }); + + it('works with negative ranges', () => { + const min = -10; + const max = -1; + for (let i = 0; i < 100; i++) { + const result = random(min, max); + expect(result).toBeGreaterThanOrEqual(min); + expect(result).toBeLessThan(max); + } + }); + + it('works with a mix of negative and positive ranges', () => { + const min = -5; + const max = 5; + for (let i = 0; i < 100; i++) { + const result = random(min, max); + expect(result).toBeGreaterThanOrEqual(min); + expect(result).toBeLessThan(max); + } + }); +}); diff --git a/src/math/random.ts b/src/math/random.ts new file mode 100644 index 000000000..5056e168b --- /dev/null +++ b/src/math/random.ts @@ -0,0 +1,20 @@ +/** + * Generate a random number within the given range. + * + * @param {number} minimum - The lower bound (inclusive). + * @param {number} maximum - The upper bound (exclusive). + * @returns {number} A random number between minimum (inclusive) and maximum (exclusive). The number can be an integer or a decimal. + * @throws {Error} Throws an error if `maximum` is not greater than `minimum`. + * + * @example + * const result1 = random(0, 5); // Returns a random number between 0 and 5. + * const result2 = random(5, 0); // If the minimum is greater than the maximum, an error is thrown + * const result3 = random(5, 5); // If the minimum is equal to the maximum, an error is thrown. + */ +export function random(minimum: number, maximum: number): number { + if (minimum >= maximum) { + throw new Error('Invalid input: The maximum value must be greater than the minimum value.'); + } + + return Math.random() * (maximum - minimum) + minimum; +} diff --git a/src/math/randomInt.spec.ts b/src/math/randomInt.spec.ts new file mode 100644 index 000000000..efaa0fe7a --- /dev/null +++ b/src/math/randomInt.spec.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from 'vitest'; +import { randomInt } from './randomInt'; + +describe('randomInt', () => { + it('generates a random integer between min (inclusive) and max (exclusive)', () => { + const min = 0; + const max = 5; + for (let i = 0; i < 100; i++) { + const result = randomInt(min, max); + expect(result).toBeGreaterThanOrEqual(min); + expect(result).toBeLessThan(max); + expect(Number.isInteger(result)).toBe(true); + } + }); + + it('throws an error if min is greater than max', () => { + const min = 5; + const max = 0; + expect(() => randomInt(min, max)).toThrow( + 'Invalid input: The maximum value must be greater than the minimum value.' + ); + }); + + it('handles edge cases where min and max are the same', () => { + const min = 5; + const max = 5; + expect(() => randomInt(min, max)).toThrow( + 'Invalid input: The maximum value must be greater than the minimum value.' + ); + }); + + it('works with negative ranges', () => { + const min = -10; + const max = -1; + for (let i = 0; i < 100; i++) { + const result = randomInt(min, max); + expect(result).toBeGreaterThanOrEqual(min); + expect(result).toBeLessThan(max); + expect(Number.isInteger(result)).toBe(true); + } + }); + + it('works with a mix of negative and positive ranges', () => { + const min = -5; + const max = 5; + for (let i = 0; i < 100; i++) { + const result = randomInt(min, max); + expect(result).toBeGreaterThanOrEqual(min); + expect(result).toBeLessThan(max); + expect(Number.isInteger(result)).toBe(true); + } + }); +}); diff --git a/src/math/randomInt.ts b/src/math/randomInt.ts new file mode 100644 index 000000000..fd30f743e --- /dev/null +++ b/src/math/randomInt.ts @@ -0,0 +1,17 @@ +import { random } from './random'; + +/** + * Generates a random integer between minimum (inclusive) and maximum (exclusive). + * + * @param {number} minimum - The lower bound (inclusive). + * @param {number} maximum - The upper bound (exclusive). + * @returns {number} A random integer between minimum (inclusive) and maximum (exclusive). + * @throws {Error} Throws an error if `maximum` is not greater than `minimum`. + * + * @example + * const result = randomInt(0, 5); // result will be a random integer between 0 (inclusive) and 5 (exclusive) + * const result2 = randomInt(5, 0); // This will throw an error + */ +export function randomInt(minimum: number, maximum: number): number { + return Math.floor(random(minimum, maximum)); +} diff --git a/src/math/round.spec.ts b/src/math/round.spec.ts index aaf7ed64f..1d97668f7 100644 --- a/src/math/round.spec.ts +++ b/src/math/round.spec.ts @@ -43,6 +43,7 @@ describe('round function', () => { // The round function in JavaScript does not work as 'Round half to Even' expect(round(-1.35, 1)).toBe(-1.3); }); + it('works with zero', () => { expect(round(0)).toBe(0); }); @@ -50,4 +51,12 @@ describe('round function', () => { it('works with precision leading to no rounding', () => { expect(round(8.88888, 5)).toBe(8.88888); }); + + it('handles edge cases where precision is not integer', () => { + const value = 1.2345; + const precision = 3.1; + expect(() => round(value, precision)).toThrow( + 'Precision must be an integer.' + ); + }); }); diff --git a/src/math/round.ts b/src/math/round.ts index 559ba64b4..7766ae73b 100644 --- a/src/math/round.ts +++ b/src/math/round.ts @@ -7,13 +7,18 @@ * @param {number} value - The number to round. * @param {number} [precision=0] - The number of decimal places to round to. Defaults to 0. * @returns {number} The rounded number. + * @throws {Error} Throws an error if `Precision` is not integer. * * @example * const result1 = round(1.2345); // result1 will be 1 * const result2 = round(1.2345, 2); // result2 will be 1.23 * const result3 = round(1.2345, 3); // result3 will be 1.235 + * const result4 = round(1.2345, 3.1); // This will throw an error */ -export function round(value: number, precision: number = 0): number { +export function round(value: number, precision = 0): number { + if (!Number.isInteger(precision)) { + throw new Error('Precision must be an integer.'); + } const multiplier = Math.pow(10, precision); return Math.round(value * multiplier) / multiplier; } diff --git a/src/object/omit.spec.ts b/src/object/omit.spec.ts index 80cf857b5..da750d5bf 100644 --- a/src/object/omit.spec.ts +++ b/src/object/omit.spec.ts @@ -4,8 +4,8 @@ import { omit } from './omit'; describe('omit', () => { it('should omit properties from an object', () => { const object = { foo: 1, bar: 2, baz: 3 }; - - expect(omit(object, ['foo', 'bar'])).toEqual({ baz: 3 }); + const result = omit(object, ['foo', 'bar']); + expect(result).toEqual({ baz: 3 }); }); it('should return an empty object if all keys are omitted', () => { diff --git a/src/object/omitBy.ts b/src/object/omitBy.ts index 70c75dda5..4a36d7230 100644 --- a/src/object/omitBy.ts +++ b/src/object/omitBy.ts @@ -5,7 +5,7 @@ * includes only the properties for which the predicate function returns false. * * @param {T} obj - The object to omit properties from. - * @param {(value: T[string], key: string) => boolean} shouldOmit - A predicate function that determines + * @param {(value: T[string], key: keyof T) => boolean} shouldOmit - A predicate function that determines * whether a property should be omitted. It takes the property's key and value as arguments and returns `true` * if the property should be omitted, and `false` otherwise. * @returns {Partial} A new object with the properties that do not satisfy the predicate function. @@ -18,7 +18,7 @@ */ export function omitBy>( obj: T, - shouldOmit: (value: T[keyof T], key: string) => boolean + shouldOmit: (value: T[keyof T], key: keyof T) => boolean ): Partial { const result: Partial = {}; diff --git a/src/object/pick.spec.ts b/src/object/pick.spec.ts index 92dcd1550..45e1175e6 100644 --- a/src/object/pick.spec.ts +++ b/src/object/pick.spec.ts @@ -4,7 +4,25 @@ import { pick } from './pick'; describe('pick', () => { it('should pick properties from an object', () => { const object = { foo: 1, bar: 2, baz: 3 }; + const result = pick(object, ['foo', 'bar']); + expect(result).toEqual({ foo: 1, bar: 2 }); + }); + + it('should return the same object if all keys are picked', () => { + const object = { a: 1, b: 2, c: 3 }; + const result = pick(object, ['a', 'b', 'c']); + expect(result).toEqual(object); + }); + + it('should return an empty object if the key array is empty', () => { + const object = { a: 1, b: 2, c: 3 }; + const result = pick(object, []); + expect(result).toEqual({}); + }); - expect(pick(object, ['foo', 'bar'])).toEqual({ foo: 1, bar: 2 }); + it('should work with nested objects', () => { + const object = { a: 1, b: { nested: 'pick' }, c: 3 }; + const result = pick(object, ['a', 'b', 'c']); + expect(result).toEqual({ a: 1, b: { nested: 'pick' }, c: 3 }); }); }); diff --git a/src/object/pickBy.ts b/src/object/pickBy.ts index b14d68bd5..fbd773d07 100644 --- a/src/object/pickBy.ts +++ b/src/object/pickBy.ts @@ -5,7 +5,7 @@ * includes only the properties for which the predicate function returns true. * * @param {T} obj - The object to pick properties from. - * @param {(value: T[keyof T], key: string) => boolean} shouldPick - A predicate function that determines + * @param {(value: T[keyof T], key: keyof T) => boolean} shouldPick - A predicate function that determines * whether a property should be picked. It takes the property's key and value as arguments and returns `true` * if the property should be picked, and `false` otherwise. * @returns {Partial} A new object with the properties that satisfy the predicate function. @@ -18,7 +18,7 @@ */ export function pickBy>( obj: T, - shouldPick: (value: T[keyof T], key: string) => boolean + shouldPick: (value: T[keyof T], key: keyof T) => boolean ): Partial { const result: Partial = {}; diff --git a/src/predicate/isUndefined.spec.ts b/src/predicate/isUndefined.spec.ts index 25c0f009a..943be236f 100644 --- a/src/predicate/isUndefined.spec.ts +++ b/src/predicate/isUndefined.spec.ts @@ -17,7 +17,7 @@ describe('isUndefined', () => { const result = arr.filter(isUndefined); - // Here the type of result should be `null[]`. + // Here the type of result should be `undefined[]`. expect(result).toStrictEqual([undefined]); }); }); diff --git a/src/promise/delay.spec.ts b/src/promise/delay.spec.ts index 5ce158b58..372814ddb 100644 --- a/src/promise/delay.spec.ts +++ b/src/promise/delay.spec.ts @@ -1,5 +1,5 @@ import { performance } from 'node:perf_hooks'; -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { delay } from './delay'; describe('delay', () => { @@ -10,4 +10,40 @@ describe('delay', () => { expect(end - start).greaterThanOrEqual(99); }); + + it('should cancel the delay if aborted via AbortSignal', async () => { + const controller = new AbortController(); + const signal = controller.signal; + + setTimeout(() => controller.abort(), 50); + + await expect(delay(100, { signal })).rejects.toThrow('The operation was aborted'); + }); + + it('should not call the delay if it is already aborted by AbortSignal', async () => { + const controller = new AbortController(); + const { signal } = controller; + const spy = vi.spyOn(global, 'setTimeout'); + + controller.abort(); + + await expect(delay(100, { signal })).rejects.toThrow('The operation was aborted'); + + expect(spy).not.toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('should clear timeout when aborted by AbortSignal', async () => { + const controller = new AbortController(); + const { signal } = controller; + const spy = vi.spyOn(global, 'clearTimeout'); + const promise = delay(100, { signal }); + + controller.abort(); + + await expect(promise).rejects.toThrow('The operation was aborted'); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); }); diff --git a/src/promise/delay.ts b/src/promise/delay.ts index f69ba6faf..ae0b3b276 100644 --- a/src/promise/delay.ts +++ b/src/promise/delay.ts @@ -1,3 +1,9 @@ +import { AbortError } from '../error/AbortError'; + +interface DelayOptions { + signal?: AbortSignal; +} + /** * Delays the execution of code for a specified number of milliseconds. * @@ -5,6 +11,8 @@ * with async/await to pause execution. * * @param {number} ms - The number of milliseconds to delay. + * @param {DelayOptions} options - The options object. + * @param {AbortSignal} options.signal - An optional AbortSignal to cancel the delay. * @returns {Promise} A Promise that resolves after the specified delay. * * @example @@ -15,9 +23,36 @@ * } * * foo(); + * + * // With AbortSignal + * const controller = new AbortController(); + * const { signal } = controller; + * + * setTimeout(() => controller.abort(), 50); // Will cancel the delay after 50ms + * try { + * await delay(100, { signal }); + * } catch (error) { + * console.error(error); // Will log 'AbortError' + * } + * } */ -export function delay(ms: number): Promise { - return new Promise(resolve => { - setTimeout(resolve, ms); +export function delay(ms: number, { signal }: DelayOptions = {}): Promise { + return new Promise((resolve, reject) => { + const abortError = () => { + reject(new AbortError()); + }; + + const abortHandler = () => { + clearTimeout(timeoutId); + abortError(); + }; + + if (signal?.aborted) { + return abortError(); + } + + const timeoutId = setTimeout(resolve, ms); + + signal?.addEventListener('abort', abortHandler, { once: true }); }); } diff --git a/vitest.config.mts b/vitest.config.mts index dcc69c7a8..55138c6a9 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -1,7 +1,9 @@ import { defineConfig } from 'vitest/config'; import packageJson from './package.json'; +import codspeedPlugin from '@codspeed/vitest-plugin'; export default defineConfig({ + ...(process.env.CI === 'true' ? { plugins: [codspeedPlugin()] } : {}), test: { name: packageJson.name, coverage: { diff --git a/yarn.lock b/yarn.lock index c5a082ae3..2a71bb91b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2016,6 +2016,30 @@ __metadata: languageName: node linkType: hard +"@codspeed/core@npm:^3.1.0": + version: 3.1.0 + resolution: "@codspeed/core@npm:3.1.0" + dependencies: + axios: "npm:^1.4.0" + find-up: "npm:^6.3.0" + form-data: "npm:^4.0.0" + node-gyp-build: "npm:^4.6.0" + checksum: 10c0/577cefd0b3b7d6eb471f1ceb24d34f5cb6a7c1bebf3f8b9aa80f0f2b8f2deffac55561e8161c497bcf39fd5effa7a39432e286131eb8f7dfece29b3d0117c075 + languageName: node + linkType: hard + +"@codspeed/vitest-plugin@npm:^3.1.0": + version: 3.1.0 + resolution: "@codspeed/vitest-plugin@npm:3.1.0" + dependencies: + "@codspeed/core": "npm:^3.1.0" + peerDependencies: + vite: ^4.2.0 || ^5.0.0 + vitest: ">=1.2.2" + checksum: 10c0/eae8a9823f77c469b59f8f109f8c4961fd8a4538b540c1493e8929bd13c6f87753caf32309fcc1ad1af0a3268d690856296db7dd89f55cf64fc3ae3f0a482338 + languageName: node + linkType: hard + "@docsearch/css@npm:3.6.0, @docsearch/css@npm:^3.6.0": version: 3.6.0 resolution: "@docsearch/css@npm:3.6.0" @@ -3736,6 +3760,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.4.0": + version: 1.7.2 + resolution: "axios@npm:1.7.2" + dependencies: + follow-redirects: "npm:^1.15.6" + form-data: "npm:^4.0.0" + proxy-from-env: "npm:^1.1.0" + checksum: 10c0/cbd47ce380fe045313364e740bb03b936420b8b5558c7ea36a4563db1258c658f05e40feb5ddd41f6633fdd96d37ac2a76f884dad599c5b0224b4c451b3fa7ae + languageName: node + linkType: hard + "babel-plugin-polyfill-corejs2@npm:^0.4.10": version: 0.4.11 resolution: "babel-plugin-polyfill-corejs2@npm:0.4.11" @@ -4266,7 +4301,7 @@ __metadata: languageName: node linkType: hard -"combined-stream@npm:^1.0.6, combined-stream@npm:~1.0.6": +"combined-stream@npm:^1.0.6, combined-stream@npm:^1.0.8, combined-stream@npm:~1.0.6": version: 1.0.8 resolution: "combined-stream@npm:1.0.8" dependencies: @@ -4849,6 +4884,7 @@ __metadata: "@babel/preset-typescript": "npm:^7.24.1" "@changesets/changelog-github": "npm:^0.5.0" "@changesets/cli": "npm:^2.27.1" + "@codspeed/vitest-plugin": "npm:^3.1.0" "@types/babel__core": "npm:^7" "@types/babel__preset-env": "npm:^7" "@types/broken-link-checker": "npm:^0" @@ -5371,6 +5407,16 @@ __metadata: languageName: node linkType: hard +"find-up@npm:^6.3.0": + version: 6.3.0 + resolution: "find-up@npm:6.3.0" + dependencies: + locate-path: "npm:^7.1.0" + path-exists: "npm:^5.0.0" + checksum: 10c0/07e0314362d316b2b13f7f11ea4692d5191e718ca3f7264110127520f3347996349bf9e16805abae3e196805814bc66ef4bff2b8904dc4a6476085fc9b0eba07 + languageName: node + linkType: hard + "find-yarn-workspace-root2@npm:1.2.16": version: 1.2.16 resolution: "find-yarn-workspace-root2@npm:1.2.16" @@ -5408,6 +5454,16 @@ __metadata: languageName: node linkType: hard +"follow-redirects@npm:^1.15.6": + version: 1.15.6 + resolution: "follow-redirects@npm:1.15.6" + peerDependenciesMeta: + debug: + optional: true + checksum: 10c0/9ff767f0d7be6aa6870c82ac79cf0368cd73e01bbc00e9eb1c2a16fbb198ec105e3c9b6628bb98e9f3ac66fe29a957b9645bcb9a490bb7aa0d35f908b6b85071 + languageName: node + linkType: hard + "for-each@npm:^0.3.3": version: 0.3.3 resolution: "for-each@npm:0.3.3" @@ -5447,6 +5503,17 @@ __metadata: languageName: node linkType: hard +"form-data@npm:^4.0.0": + version: 4.0.0 + resolution: "form-data@npm:4.0.0" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + mime-types: "npm:^2.1.12" + checksum: 10c0/cb6f3ac49180be03ff07ba3ff125f9eba2ff0b277fb33c7fc47569fc5e616882c5b1c69b9904c4c4187e97dd0419dd03b134174756f296dec62041e6527e2c6e + languageName: node + linkType: hard + "form-data@npm:~2.3.2": version: 2.3.3 resolution: "form-data@npm:2.3.3" @@ -6664,6 +6731,15 @@ __metadata: languageName: node linkType: hard +"locate-path@npm:^7.1.0": + version: 7.2.0 + resolution: "locate-path@npm:7.2.0" + dependencies: + p-locate: "npm:^6.0.0" + checksum: 10c0/139e8a7fe11cfbd7f20db03923cacfa5db9e14fa14887ea121345597472b4a63c1a42a8a5187defeeff6acf98fd568da7382aa39682d38f0af27433953a97751 + languageName: node + linkType: hard + "lodash.clonedeep@npm:^4.5.0": version: 4.5.0 resolution: "lodash.clonedeep@npm:4.5.0" @@ -7149,6 +7225,17 @@ __metadata: languageName: node linkType: hard +"node-gyp-build@npm:^4.6.0": + version: 4.8.1 + resolution: "node-gyp-build@npm:4.8.1" + bin: + node-gyp-build: bin.js + node-gyp-build-optional: optional.js + node-gyp-build-test: build-test.js + checksum: 10c0/e36ca3d2adf2b9cca316695d7687207c19ac6ed326d6d7c68d7112cebe0de4f82d6733dff139132539fcc01cf5761f6c9082a21864ab9172edf84282bc849ce7 + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 10.1.0 resolution: "node-gyp@npm:10.1.0" @@ -7403,6 +7490,15 @@ __metadata: languageName: node linkType: hard +"p-limit@npm:^4.0.0": + version: 4.0.0 + resolution: "p-limit@npm:4.0.0" + dependencies: + yocto-queue: "npm:^1.0.0" + checksum: 10c0/a56af34a77f8df2ff61ddfb29431044557fcbcb7642d5a3233143ebba805fc7306ac1d448de724352861cb99de934bc9ab74f0d16fe6a5460bdbdf938de875ad + languageName: node + linkType: hard + "p-limit@npm:^5.0.0": version: 5.0.0 resolution: "p-limit@npm:5.0.0" @@ -7430,6 +7526,15 @@ __metadata: languageName: node linkType: hard +"p-locate@npm:^6.0.0": + version: 6.0.0 + resolution: "p-locate@npm:6.0.0" + dependencies: + p-limit: "npm:^4.0.0" + checksum: 10c0/d72fa2f41adce59c198270aa4d3c832536c87a1806e0f69dffb7c1a7ca998fb053915ca833d90f166a8c082d3859eabfed95f01698a3214c20df6bb8de046312 + languageName: node + linkType: hard + "p-map@npm:^2.0.0": version: 2.1.0 resolution: "p-map@npm:2.1.0" @@ -7497,6 +7602,13 @@ __metadata: languageName: node linkType: hard +"path-exists@npm:^5.0.0": + version: 5.0.0 + resolution: "path-exists@npm:5.0.0" + checksum: 10c0/b170f3060b31604cde93eefdb7392b89d832dfbc1bed717c9718cbe0f230c1669b7e75f87e19901da2250b84d092989a0f9e44d2ef41deb09aa3ad28e691a40a + languageName: node + linkType: hard + "path-is-absolute@npm:^1.0.0": version: 1.0.1 resolution: "path-is-absolute@npm:1.0.1" @@ -7733,6 +7845,13 @@ __metadata: languageName: node linkType: hard +"proxy-from-env@npm:^1.1.0": + version: 1.1.0 + resolution: "proxy-from-env@npm:1.1.0" + checksum: 10c0/fe7dd8b1bdbbbea18d1459107729c3e4a2243ca870d26d34c2c1bcd3e4425b7bcc5112362df2d93cc7fb9746f6142b5e272fd1cc5c86ddf8580175186f6ad42b + languageName: node + linkType: hard + "prr@npm:~1.0.1": version: 1.0.1 resolution: "prr@npm:1.0.1"