├── .github
└── workflows
│ ├── main.yml
│ └── size.yml
├── .gitignore
├── LICENSE
├── README.md
├── docs
├── .vuepress
│ ├── config.ts
│ ├── midash.ts
│ └── public
│ │ └── midash.svg
├── README.md
├── api.md
└── zh
│ ├── Readme.md
│ └── api.md
├── package.json
├── pnpm-lock.yaml
├── src
├── assign.ts
├── async.ts
├── camelCase.ts
├── castArray.ts
├── chunk.ts
├── clone.ts
├── compact.ts
├── compose.ts
├── debounce.ts
├── defaults.ts
├── difference.ts
├── get.ts
├── groupBy.ts
├── head.ts
├── index.ts
├── intersection.ts
├── isArray.ts
├── isBoolean.ts
├── isEqual.ts
├── isObject.ts
├── isPlainObject.ts
├── isPrimitive.ts
├── isPromise.ts
├── isTypedArray.ts
├── kebabCase.ts
├── keyBy.ts
├── mapKeys.ts
├── max.ts
├── memoize.ts
├── merge.ts
├── min.ts
├── nth.ts
├── omit.ts
├── omitBy.ts
├── once.ts
├── pick.ts
├── pickBy.ts
├── property.ts
├── random.ts
├── range.ts
├── sample.ts
├── sampleSize.ts
├── shuffle.ts
├── snakeCase.ts
├── sum.ts
├── template.ts
├── throttle.ts
├── uniq.ts
├── uniqBy.ts
├── unzip.ts
├── words.ts
└── zip.ts
├── test
├── assign.spec.ts
├── async.spec.ts
├── camelCase.spec.ts
├── castArray.spec.ts
├── chunk.spec.ts
├── clone.spec.ts
├── compact.spec.ts
├── compose.spec.ts
├── debounce.spec.ts
├── defaults.spec.ts
├── difference.spec.ts
├── get.spec.ts
├── groupBy.spec.ts
├── head.spec.ts
├── intersection.spec.ts
├── isArray.spec.ts
├── isBoolean.ts
├── isEqual.spec.ts
├── isObject.spec.ts
├── isPlainObject.spec.ts
├── isPromise.spec.ts
├── isTypedArray.spec.ts
├── kebabCase.spec.ts
├── keyBy.spec.ts
├── mapKeys.spec.ts
├── max.spec.ts
├── memoize.spec.ts
├── merge.spec.ts
├── min.spec.ts
├── nth.spec.ts
├── omit.spec.ts
├── omitBy.spec.ts
├── once.spec.ts
├── pick.spec.ts
├── pickBy.spec.ts
├── property.spec.ts
├── random.spec.ts
├── range.spec.ts
├── sample.spec.ts
├── sampleSize.spec.ts
├── shuffle.spec.ts
├── snakeCase.spec.ts
├── sum.spec.ts
├── template.spec.ts
├── throttle.spec.ts
├── uniq.spec.ts
├── uniqBy.spec.ts
├── unzip.spec.ts
└── zip.spec.ts
├── tsconfig.json
├── tsup.config.ts
└── vitest.config.ts
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: [push]
3 | jobs:
4 | build:
5 | name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }}
6 |
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | matrix:
10 | node: ['16.x', '18.x']
11 | os: [ubuntu-latest]
12 |
13 | steps:
14 | - name: Checkout repo
15 | uses: actions/checkout@v3
16 |
17 | - name: Use Node ${{ matrix.node }}
18 | uses: actions/setup-node@v3
19 | with:
20 | node-version: ${{ matrix.node }}
21 |
22 | - name: Setup pnpm
23 | run: corepack enable
24 |
25 | - name: Install deps and build (with cache)
26 | run: pnpm i
27 |
28 | - name: Lint
29 | run: pnpm lint
30 |
31 | - name: Test
32 | run: pnpm test -- run --coverage
33 |
34 | - name: Build
35 | run: pnpm build
36 |
--------------------------------------------------------------------------------
/.github/workflows/size.yml:
--------------------------------------------------------------------------------
1 | name: size
2 | on: [pull_request]
3 | jobs:
4 | size:
5 | runs-on: ubuntu-latest
6 | env:
7 | CI_JOB_NUMBER: 1
8 | steps:
9 | - uses: actions/checkout@v3
10 |
11 | - uses: andresz1/size-limit-action@v1.7.0
12 | with:
13 | github_token: ${{ secrets.GITHUB_TOKEN }}
14 | package_manager: pnpm
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | dist
5 | package-lock.json
6 | .cache
7 | .temp
8 | .idea
9 | coverage
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 shfshanyue
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Midash
2 |
3 | [](https://npmjs.com/package/midash)
4 | 
5 | 
6 | 
7 | 
8 | [](https://unpkg.com/midash/dist/index.esm.js)
9 |
10 | An alternative to `lodash` with the same API, plus additional async utilities.
11 |
12 | + 🔨 High frequency API
13 | + 🕒 Familiar lodash API
14 | + 💪 Support Tree Shaking
15 | + 👫 Support Typescript
16 | + 🔥 Smaller Size (with ES6+ API)
17 | + 📦 2.5kb mini library
18 | + 🚀 Additional async utilities
19 |
20 | ## API
21 |
22 | [Documentation](https://midash.devtool.tech/) [中文文档](https://midash.devtool.tech/zh/api.html)
23 |
24 | ## Installation
25 |
26 | ``` bash
27 | # yarn
28 | $ yarn add midash
29 | # pnpm
30 | $ pnpm i midash
31 | ```
32 |
33 | ## Usage
34 |
35 | ``` js
36 | import { sum } from 'midash'
37 |
38 | sum([1, 3, 5, 7, 9])
39 | ```
40 |
41 | ## Async Utilities
42 |
43 | Midash provides several async utilities:
44 |
45 | - `sleep(ms)`: Pause execution for a specified number of milliseconds.
46 | - `retry(fn, options)`: Retry a function multiple times with customizable options.
47 | - `map(iterable, mapper, options)`: Asynchronously map over an iterable with concurrency control.
48 | - `filter(iterable, filterer, options)`: Asynchronously filter an iterable with concurrency control.
49 |
50 | Example usage:
51 |
52 | ```js
53 | import { sleep, retry, map, filter } from 'midash'
54 |
55 | // Sleep for 1 second
56 | await sleep(1000)
57 |
58 | // Retry a function up to 3 times
59 | const result = await retry(async () => {
60 | // Your async operation here
61 | }, { times: 3 })
62 |
63 | // Asynchronously map over an array with a concurrency of 2
64 | const mappedResults = await map([1, 2, 3, 4], async (num) => {
65 | await sleep(100)
66 | return num * 2
67 | }, { concurrency: 2 })
68 |
69 | // Asynchronously filter an array
70 | const filteredResults = await filter([1, 2, 3, 4, 5], async (num) => {
71 | await sleep(100)
72 | return num % 2 === 0
73 | })
74 | ```
75 |
76 | These async utilities make Midash a powerful choice for both synchronous and asynchronous operations in modern JavaScript applications.
--------------------------------------------------------------------------------
/docs/.vuepress/config.ts:
--------------------------------------------------------------------------------
1 | import { defaultTheme } from '@vuepress/theme-default'
2 | import { path } from '@vuepress/utils'
3 |
4 | export default {
5 | head: [
6 | [
7 | 'link',
8 | {
9 | rel: 'icon',
10 | type: 'image/svg+xml',
11 | sizes: '16x16',
12 | href: `/midash.svg`,
13 | },
14 | ],
15 | ],
16 | locales: {
17 | '/': {
18 | lang: 'en-US',
19 | title: 'midash',
20 | description: 'An alternative to lodash with the same API.',
21 | },
22 | '/zh/': {
23 | lang: 'zh-CN',
24 | title: 'midash',
25 | description: '与 lodash 拥有相似 API,基于 ES6+,体积更小的工具函数库',
26 | },
27 | },
28 | theme: defaultTheme({
29 | logo: '/midash.svg',
30 | home: '/',
31 | docsRepo: 'shfshanyue/midash',
32 | repo: 'shfshanyue/midash',
33 | locales: {
34 | /**
35 | * English locale config
36 | *
37 | * As the default locale of @vuepress/theme-default is English,
38 | * we don't need to set all of the locale fields
39 | */
40 | '/': {
41 | // navbar
42 | navbar: [
43 | {
44 | text: 'Home',
45 | link: '/',
46 | },
47 | {
48 | text: 'API Documentation',
49 | link: '/api',
50 | },
51 | ],
52 | editLinkText: 'Edit this page on GitHub',
53 | },
54 |
55 | /**
56 | * Chinese locale config
57 | */
58 | '/zh/': {
59 | // navbar
60 | navbar: [
61 | {
62 | text: '首页',
63 | link: '/',
64 | },
65 | {
66 | text: 'API 文档',
67 | link: '/zh/api',
68 | },
69 | ],
70 | selectLanguageName: '简体中文',
71 | selectLanguageText: '选择语言',
72 | selectLanguageAriaLabel: '选择语言',
73 | editLinkText: '在 GitHub 上编辑此页',
74 | lastUpdatedText: '上次更新',
75 | contributorsText: '贡献者',
76 | tip: '提示',
77 | warning: '注意',
78 | danger: '警告',
79 | // 404 page
80 | notFound: [
81 | '这里什么都没有',
82 | '我们怎么到这来了?',
83 | '这是一个 404 页面',
84 | '看起来我们进入了错误的链接',
85 | ],
86 | backToHome: '返回首页',
87 | // a11y
88 | openInNewWindow: '在新窗口打开',
89 | toggleColorMode: '切换颜色模式',
90 | toggleSidebar: '切换侧边栏',
91 | },
92 | },
93 | }),
94 | plugins: [
95 | {
96 | name: 'midash API',
97 | clientConfigFile: path.resolve(__dirname, './midash.ts'),
98 | },
99 | ],
100 | }
101 |
--------------------------------------------------------------------------------
/docs/.vuepress/midash.ts:
--------------------------------------------------------------------------------
1 | import { defineClientConfig } from '@vuepress/client'
2 | import pkg from '../../package.json'
3 |
4 | export default defineClientConfig({
5 | async enhance(context) {
6 | import('../..').then((o) => {
7 | console.log(
8 | 'You can try midash API using variable %c_%c or %cmidash%c in browser devtools.',
9 | 'font-style: italic; color: red; font-size: 1.5em;', 'font-style: normal',
10 | 'font-style: italic; color: red; font-size: 1.5em;', 'font-style: normal',
11 | )
12 | console.log(
13 | `Version: %cmidash v${pkg.version}%c`,
14 | 'font-style: italic; color: red; font-size: 1.5em;', 'font-style: normal',
15 | )
16 | globalThis.midash = o
17 | globalThis._ = o
18 | })
19 |
20 | // context.app.use(context.router)
21 | // if (globalThis.navigator?.language?.includes('zh')) {
22 | // context.router.push('/zh/')
23 | // }
24 | },
25 | })
--------------------------------------------------------------------------------
/docs/.vuepress/public/midash.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar: false
3 | ---
4 |
5 | # midash
6 |
7 | [](https://npmjs.com/package/midash)
8 | 
9 | 
10 | 
11 | 
12 | 
13 |
14 | An alternative to `lodash` with the same API, plus additional async utilities.
15 |
16 | + 🔨 High frequency API
17 | + 🕒 Familiar lodash API
18 | + 💪 Support Tree Shaking
19 | + 👫 Support Typescript
20 | + 🔥 Smaller Size (with ES6+ API)
21 | + 📦 2.5kb mini library
22 | + 🚀 Additional async utilities
23 |
24 | ## API
25 |
26 | [Documentation](https://midash.devtool.tech/) [中文文档](https://midash.devtool.tech/zh/api.html)
27 |
28 | ## Installation
29 |
30 | ``` bash
31 | # yarn
32 | $ yarn add midash
33 | # pnpm
34 | $ pnpm i midash
35 | ```
36 |
37 | ``` js
38 | import { sum } from 'midash'
39 |
40 | sum([1, 3, 5, 7, 9])
41 | ```
42 |
43 | ## Async Utilities
44 |
45 | Midash provides several async utilities:
46 |
47 | - `sleep(ms)`: Pause execution for a specified number of milliseconds.
48 | - `retry(fn, options)`: Retry a function multiple times with customizable options.
49 | - `map(iterable, mapper, options)`: Asynchronously map over an iterable with concurrency control.
50 | - `filter(iterable, filterer, options)`: Asynchronously filter an iterable with concurrency control.
51 |
52 | Example usage:
53 |
54 | ```js
55 | import { sleep, retry, map, filter } from 'midash'
56 |
57 | // Sleep for 1 second
58 | await sleep(1000)
59 |
60 | // Retry a function up to 3 times
61 | const result = await retry(async () => {
62 | // Your async operation here
63 | }, { times: 3 })
64 |
65 | // Asynchronously map over an array with a concurrency of 2
66 | const mappedResults = await map([1, 2, 3, 4], async (num) => {
67 | await sleep(100)
68 | return num * 2
69 | }, { concurrency: 2 })
70 |
71 | // Asynchronously filter an array
72 | const filteredResults = await filter([1, 2, 3, 4, 5], async (num) => {
73 | await sleep(100)
74 | return num % 2 === 0
75 | })
76 | ```
--------------------------------------------------------------------------------
/docs/api.md:
--------------------------------------------------------------------------------
1 | # midash API
2 |
3 | ## Object
4 |
5 | ### get
6 |
7 | Get the attribute of object deeply.
8 |
9 | ``` js
10 | const object = { a: [{ b: 3 }] }
11 |
12 | // => 3
13 | _.get(object, 'a[0].b')
14 |
15 | // => 3
16 | _.get(object, ['a', '0', 'b'])
17 |
18 | // => 'default'
19 | _.get(object, 'a.b.c', 'default')
20 | ```
21 |
22 | ### omit
23 |
24 | Ignore attributes of object and return new object.
25 |
26 | ::: warning
27 | `_.omit(object, 'a', 'b')` can't work well in midash. use `_.omit(object, ['a', 'b'])` instead.
28 | :::
29 |
30 | ``` js
31 | const object = {
32 | a: 3,
33 | b: 4,
34 | c: 5
35 | }
36 |
37 | //=> { c: 5 }
38 | _.omit(object, ['a', 'b'])
39 | ```
40 |
41 | ### omitBy
42 |
43 | Ignore attributes of object by function and return new object.
44 |
45 | ``` js
46 | const object = {
47 | a: 3,
48 | b: 4,
49 | c: 5
50 | }
51 |
52 | // omit by value
53 | //=> { b:4, c: 5 }
54 | _.omitBy(object, value => value === 3)
55 |
56 | // omit by key
57 | //=> { b:4, c: 5 }
58 | _.omitBy(object, (value, key) => key === 'a')
59 | ```
60 |
61 | ### pick
62 |
63 | Pick attributes of object by function and return new object.
64 |
65 | ::: warning
66 | `_.pick(object, 'a', 'b')` can't work well in midash, use `_.pick(object, ['a', 'b'])` instead.
67 | :::
68 |
69 | ``` js
70 | const object = {
71 | a: 3,
72 | b: 4,
73 | c: undefined
74 | }
75 |
76 | //=> { a: 3, b: 4 }
77 | _.pick(object, ['a', 'b'])
78 |
79 | //=> {}
80 | _.pick(object, ['z'])
81 |
82 | //=> { c: undefined }
83 | _.pick(object, ['c'])
84 | ```
85 |
86 | ### pickBy
87 |
88 | Pick attributes of object by function and return new object.
89 |
90 | ``` js
91 | const object = {
92 | a: 3,
93 | b: 4,
94 | }
95 |
96 | //=> { a: 3 }
97 | _.pickBy(object, value => value === 3)
98 |
99 | //=> { a: 3 }
100 | _.pickBy(object, (value, key) => key === 'a')
101 | ```
102 |
103 | ### defaults
104 |
105 | Assigns own enumerable properties of source objects to the destination object for all destination properties that resolve to undefined.
106 |
107 | ``` js
108 | //=> { mode: 'development', sourcemap: true, devtool: true }
109 | _.defaults({
110 | mode: 'development',
111 | sourcemap: true
112 | }, {
113 | mode: 'production',
114 | devtool: true
115 | })
116 | ```
117 |
118 | ### clone
119 |
120 | Creates a shallow clone of an object.
121 |
122 | ``` js
123 | const o = { a: { aa: 3 }, b: 4 }
124 |
125 | //=> true
126 | _.clone(o).a === o.a
127 | ```
128 |
129 | ### merge
130 |
131 | Merges one or more objects into first object recursively and returns new object.
132 |
133 | ``` js
134 | //=> { a: 4, b: 2 }
135 | _.merge({ a: 1 }, { b: 2 }, { a: 3 }, { a: 4 })
136 | ```
137 |
138 | ### assign
139 |
140 | Assigns own enumerable string keyed properties of source objects to the destination object. Source objects are applied from left to right. Subsequent sources overwrite property assignments of previous sources.
141 |
142 | ``` js
143 | // => { a: 1, b: 3, c: 5, d: 6 }
144 | _.assign({ a: 1, b: 2 }, { b: 3, c: 4 }, { c: 5, d: 6 })
145 | ```
146 |
147 | ### mapKeys
148 |
149 | Transform the keys of an object with a function and returns a new object.
150 |
151 | ``` js
152 | //=> { a3: 3, b4: 4 }
153 | _.mapKeys({ a: 3, b: 4 }, (v, k) => `${k}${v}`)
154 | ```
155 |
156 | ## Array
157 |
158 | ### chunk
159 |
160 | Get an array of elements split into chunk by size.
161 |
162 | ``` js
163 | //=> [[0, 1, 2], [3, 4, 5]]
164 | _.chunk([0, 1, 2, 3, 4, 5], 3)
165 |
166 | //=> [[0], [1], [2]]
167 | _.chunk([0, 1, 2])
168 |
169 | //=> [['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']]
170 | _.chunk('abcdefghi', 3)
171 | ```
172 |
173 | ### sample
174 |
175 | Get a random element from an array.
176 |
177 | ``` js
178 | // get a random element from [0, 3, 6, 10]
179 | _.sample([0, 3, 6, 10])
180 |
181 | //=> undefined
182 | _.sample([])
183 | ```
184 |
185 | ### sampleSize
186 |
187 | Get `n` random element from an array.
188 |
189 | ``` js
190 | //=> Maybe [1, 2]
191 | _.sampleSize([1, 2, 3], 2)
192 |
193 | //=> [1, 2, 3]
194 | _.sampleSize([1, 2, 3], 4)
195 | ```
196 |
197 | ### shuffle
198 |
199 | Creates an array of shuffled values.
200 |
201 | ``` js
202 | //=> [2, 3, 1] (any random order)
203 | _.shuffle([1, 2, 3])
204 | ```
205 |
206 | ### difference/differenceBy
207 |
208 | Creates an array of array values not included in the other given arrays.
209 |
210 | ::: tip
211 | In `midash`, `differenceBy` is an alias of `difference`.
212 | :::
213 |
214 | ``` js
215 | //=> [2, 4]
216 | _.difference([1, 2, 3, 4], [1, 3, 5])
217 |
218 | //=> [{ a: 4 }]
219 | _.differenceBy([{ a: 3 }, { a: 4 }], [{ a: 3 }], x => x.a)
220 | ```
221 |
222 | ### intersection
223 |
224 | Creates an array of unique values that are included in all given arrays.
225 |
226 | ``` js
227 | //=> [2]
228 | _.intersection([1, 2], [2, 3])
229 |
230 | //=> [{ id: 1 }]
231 | _.intersection([{ id: 1 }, { id: 2 }], [{ id: 1 }, { id: 3 }], item => item.id)
232 | ```
233 |
234 | ### uniq
235 |
236 | Creates a duplicate-free version of an array.
237 |
238 | ``` js
239 | //=> [1, 2, 3]
240 | _.uniq([1, 2, 3, 1, 2])
241 | ```
242 |
243 | ### uniqBy
244 |
245 | Creates a duplicate-free version of an array using a function for comparison.
246 |
247 | ``` js
248 | //=> [{ id: 1 }, { id: 2 }]
249 | _.uniqBy([{ id: 1 }, { id: 2 }, { id: 1 }], item => item.id)
250 | ```
251 |
252 | ### keyBy
253 |
254 | Creates an object composed of keys generated from the results of running each element of collection through iteratee.
255 |
256 | ``` js
257 | const list = [
258 | { id: 1, name: 'hello' },
259 | { id: 2, name: 'world' },
260 | ]
261 |
262 | //=> { '1': { id: 1, name: 'hello' }, '2': { id: 2, name: 'world' } }
263 | _.keyBy(list, x => x.id)
264 | ```
265 |
266 | ### groupBy
267 |
268 | Creates an object composed of keys generated from the results of running each element of collection through iteratee. The corresponding value of each key is an array of elements responsible for generating the key.
269 |
270 | ``` js
271 | //=> { '3': ['one', 'two'], '5': ['three'] }
272 | _.groupBy(['one', 'two', 'three'], x => x.length)
273 | ```
274 |
275 | ### zip
276 |
277 | Creates an array of grouped elements, the first of which contains the first elements of the given arrays, the second of which contains the second elements of the given arrays, and so on.
278 |
279 | ``` js
280 | // => [[1, 'a', true],[2, 'b', false],[3, 'c', undefined]];
281 | _.zip([1, 2, 3], ['a', 'b', 'c'], [true, false]);
282 |
283 | // => [[undefined, 1], [undefined, 2], [undefined, 3]];
284 | _.zip([],[1, 2, 3])
285 |
286 | // => [[1, 'a', undefined], [2, 'b', undefined],[undefined, 'c', undefined]];
287 | _.zip([1, 2], ['a', 'b', 'c'], [])
288 | ```
289 |
290 | ### unzip
291 |
292 | This method is like _.zip except that it accepts an array of grouped elements and creates an array regrouping the elements to their pre-zip configuration.
293 |
294 | ``` js
295 | // => [[1, 2, 3], ['a', 'b', 'c'], [true, false, true]]
296 | _.unzip([[1, 'a', true], [2, 'b', false], [3, 'c', true]])
297 |
298 | // => []
299 | _.unzip([])
300 |
301 | // => [[1, 2, 4], ['a', 'b', 'c'], [undefined, 3, undefined]]
302 | _.unzip([[1, 'a'], [2, 'b', 3], [4, 'c']])
303 | ```
304 |
305 | ### compact
306 |
307 | Removes all falsey values from an array.
308 |
309 | ``` js
310 | // => [1, 2, 3]
311 | _.compact([0, 1, false, 2, '', 3])
312 | ```
313 |
314 | ### head
315 |
316 | Gets the first element of array.
317 |
318 | ``` js
319 | // => 1
320 | _.head([1, 2, 3])
321 |
322 | // => undefined
323 | _.head([])
324 | ```
325 |
326 | ### nth
327 |
328 | Gets the element at index n of array. If n is negative, the nth element from the end is returned.
329 |
330 | ``` js
331 | // => 2
332 | _.nth([1, 2, 3], 1)
333 |
334 | // => 3
335 | _.nth([1, 2, 3], -1)
336 | ```
337 |
338 | ## String
339 |
340 | ### camelCase
341 |
342 | Converts string to camel case.
343 |
344 | ``` js
345 | // => 'fooBar'
346 | _.camelCase('foo_bar')
347 |
348 | // => 'fooBar'
349 | _.camelCase('foo-bar')
350 |
351 | // => 'fooBar'
352 | _.camelCase('Foo Bar')
353 | ```
354 |
355 | ### snakeCase
356 |
357 | Converts string to snake case.
358 |
359 | ``` js
360 | // => 'foo_bar'
361 | _.snakeCase('fooBar')
362 |
363 | // => 'foo_bar'
364 | _.snakeCase('foo-bar')
365 |
366 | // => 'foo_bar'
367 | _.snakeCase('Foo Bar')
368 | ```
369 |
370 | ### kebabCase
371 |
372 | Converts string to kebab case.
373 |
374 | ``` js
375 | // => 'foo-bar'
376 | _.kebabCase('fooBar')
377 |
378 | // => 'foo-bar'
379 | _.kebabCase('foo_bar')
380 |
381 | // => 'foo-bar'
382 | _.kebabCase('Foo Bar')
383 | ```
384 |
385 | ### words
386 |
387 | Splits string into an array of its words.
388 |
389 | ``` js
390 | // => ['foo', 'bar']
391 | _.words('foo bar')
392 |
393 | // => ['foo', 'bar']
394 | _.words('foo-bar', /[^, -]+/g)
395 | ```
396 |
397 | ### template
398 |
399 | Creates a compiled template function that can interpolate data properties.
400 |
401 | ``` js
402 | // => 'Hello Fred!'
403 | _.template('Hello ${name}!')({ name: 'Fred' })
404 | ```
405 |
406 | ## Number
407 |
408 | ### random
409 |
410 | Gets a random integer between min and max (inclusive).
411 |
412 | ``` js
413 | // an integer between 10 and 20, includes 10 and 20
414 | _.random(10, 20)
415 |
416 | // an integer between 0 and 20
417 | _.random(20)
418 |
419 | // an integer between 0 and 1
420 | _.random()
421 | ```
422 |
423 | ### range
424 |
425 | Creates an array of numbers (positive and/or negative) progressing from start up to, but not including, end.
426 |
427 | ``` js
428 | //=> [0, 1, 2, 3]
429 | _.range(4)
430 |
431 | //=> [0, -1, -2, -3]
432 | _.range(-4)
433 |
434 | //=> [1, 2, 3, 4]
435 | _.range(1, 5)
436 |
437 | //=> [5, 4, 3, 2]
438 | _.range(5, 1)
439 |
440 | //=> [0, -1, -2, -3]
441 | _.range(0, -4, -1)
442 | ```
443 |
444 | ## Lang
445 |
446 | ### castArray
447 |
448 | Casts value as an array if it's not one.
449 |
450 | ``` js
451 | _.castArray(1);
452 | // => [1]
453 |
454 | _.castArray({ 'a': 1 });
455 | // => [{ 'a': 1 }]
456 |
457 | _.castArray('abc');
458 | // => ['abc']
459 |
460 | _.castArray(null);
461 | // => [null]
462 |
463 | _.castArray(undefined);
464 | // => [undefined]
465 |
466 | _.castArray();
467 | // => []
468 |
469 | const array = [1, 2, 3];
470 | console.log(_.castArray(array) === array);
471 | // => true
472 | ```
473 |
474 | ### isArray
475 |
476 | Checks if value is classified as an Array object.
477 |
478 | ``` js
479 | //=> true
480 | _.isArray([])
481 |
482 | //=> false
483 | _.isArray({})
484 | ```
485 |
486 | ### isBoolean
487 |
488 | Checks if value is classified as a boolean primitive.
489 |
490 | ``` js
491 | //=> true
492 | _.isBoolean(false)
493 |
494 | //=> true
495 | _.isBoolean(true)
496 |
497 | //=> false
498 | _.isBoolean(null)
499 | ```
500 |
501 | ### isObject
502 |
503 | Checks if value is the language type of Object.
504 |
505 | ``` js
506 | //=> true
507 | _.isObject({})
508 |
509 | //=> true
510 | _.isObject([])
511 |
512 | //=> true
513 | _.isObject(x => {})
514 |
515 | //=> false
516 | _.isObject(null)
517 | ```
518 |
519 | ### isPlainObject
520 |
521 | Checks if value is a plain object.
522 |
523 | ``` js
524 | //=> true
525 | _.isPlainObject({})
526 |
527 | //=> true
528 | _.isPlainObject(Object.create(null))
529 |
530 | //=> false
531 | _.isPlainObject([])
532 |
533 | //=> false
534 | _.isPlainObject(new Date())
535 | ```
536 |
537 | ### isPromise
538 |
539 | Checks if value is a Promise.
540 |
541 | ``` js
542 | //=> true
543 | _.isPromise(Promise.resolve())
544 |
545 | //=> false
546 | _.isPromise({})
547 | ```
548 |
549 | ### isPrimitive
550 |
551 | Checks if value is primitive.
552 |
553 | ``` js
554 | //=> true
555 | _.isPrimitive(null)
556 |
557 | //=> true
558 | _.isPrimitive(undefined)
559 |
560 | //=> true
561 | _.isPrimitive(1)
562 |
563 | //=> true
564 | _.isPrimitive('string')
565 |
566 | //=> true
567 | _.isPrimitive(true)
568 |
569 | //=> true
570 | _.isPrimitive(Symbol())
571 |
572 | //=> false
573 | _.isPrimitive({})
574 |
575 | //=> false
576 | _.isPrimitive([])
577 | ```
578 |
579 | ### isTypedArray
580 |
581 | Checks if value is classified as a typed array.
582 |
583 | ``` js
584 | //=> true
585 | _.isTypedArray(new Uint8Array([1, 2, 3]))
586 |
587 | //=> false
588 | _.isTypedArray([])
589 | ```
590 |
591 | ### isEqual
592 |
593 | Performs a deep comparison between two values to determine if they are equivalent.
594 |
595 | ``` js
596 | //=> true
597 | _.isEqual([1, 2, 3], [1, 2, 3])
598 |
599 | //=> false
600 | _.isEqual([1, 2, 3], [1, 2, 4])
601 |
602 | //=> true
603 | _.isEqual({ a: 1, b: 2 }, { b: 2, a: 1 })
604 | ```
605 |
606 | ## Function
607 |
608 | ### compose/flowRight
609 |
610 | Composes functions from right to left. The rightmost function can take multiple arguments, the remaining functions must be unary.
611 |
612 | ::: tip
613 | In `midash`, `flowRight` is an alias of `compose`.
614 | :::
615 |
616 | ``` js
617 | const double = x => x * 2
618 | const square = x => x * x
619 |
620 | //=> 200
621 | _.compose(double, square)(10)
622 | _.flowRight(double, square)(10)
623 | ```
624 |
625 | ### property
626 |
627 | Creates a function that returns the value at path of a given object.
628 |
629 | ``` js
630 | const objects = [
631 | { 'a': { 'b': 2 } },
632 | { 'a': { 'b': 1 } }
633 | ];
634 | // => [2, 1]
635 | _.map(objects, _.property('a.b'));
636 |
637 | // => [1, 2]
638 | _.map(_.sortBy(objects, _.property(['a', 'b'])), 'a.b');
639 | ```
640 |
641 | ### once
642 |
643 | Creates a function that is restricted to be called only once. Repeat calls to the function return the value of the first invocation.
644 |
645 | ``` js
646 | // `initialize` can only call `createApplication` once.
647 | const initialize = _.once(createApplication);
648 | initialize();
649 | initialize(); // No effect
650 | ```
651 |
652 | ### memoize
653 |
654 | Creates a function that memoizes the result of func.
655 |
656 | ``` js
657 | const object = { 'a': 1, 'b': 2 };
658 | const other = { 'c': 3, 'd': 4 };
659 |
660 | // => [1, 2]
661 | const values = _.memoize(Object.values);
662 | values(object);
663 |
664 | // => [3, 4]
665 | values(other);
666 |
667 | object.a = 2;
668 | // => [1, 2] (cached result)
669 | values(object);
670 | ```
671 |
672 | ### debounce
673 |
674 | Creates a debounced function that delays invoking func until after wait milliseconds have elapsed since the last time the debounced function was invoked.
675 |
676 | ``` js
677 | // Create a debounced function that will only invoke updateChart
678 | // after waiting at least 200ms from the last time it was called
679 | const debouncedUpdate = _.debounce(updateChart, 200);
680 |
681 | // Call it multiple times
682 | window.addEventListener('resize', debouncedUpdate);
683 | ```
684 |
685 | ### throttle
686 |
687 | Creates a throttled function that only invokes func at most once per every wait milliseconds.
688 |
689 | ``` js
690 | // Create a throttled function that only invokes saveInput
691 | // at most once every 500ms
692 | const throttledSave = _.throttle(saveInput, 500);
693 |
694 | // Call it multiple times
695 | inputField.addEventListener('input', throttledSave);
696 | ```
697 |
698 | ## Math
699 |
700 | ### sum
701 |
702 | Computes the sum of the values in array.
703 |
704 | ``` js
705 | // => 6
706 | _.sum([1, 2, 3])
707 |
708 | // => 0
709 | _.sum([])
710 | ```
711 |
712 | ### max
713 |
714 | Gets the maximum value of collection. If collection is empty or falsey, undefined is returned.
715 |
716 | ::: tip
717 | In `midash`, `maxBy` is an alias of `max`.
718 | :::
719 |
720 | ``` js
721 | // => 5
722 | _.max([-5, -3, 0, 3, 5])
723 |
724 | // => { a: 4 }
725 | _.maxBy([
726 | { a: 3 },
727 | { a: 4 }
728 | ], x => x.a)
729 | ```
730 |
731 | ### min
732 |
733 | Gets the minimum value of collection. If collection is empty or falsey, undefined is returned.
734 |
735 | ::: tip
736 | In `midash`, `minBy` is an alias of `min`.
737 | :::
738 |
739 | ``` js
740 | // => -5
741 | _.min([-5, -3, 0, 3, 5])
742 |
743 | // => { a: 3 }
744 | _.minBy([
745 | { a: 3 },
746 | { a: 4 }
747 | ], x => x.a)
748 | ```
749 |
750 | ## Async
751 |
752 | ### sleep
753 |
754 | Creates a Promise that resolves after the specified milliseconds.
755 |
756 | ``` js
757 | // Pause execution for 1 second
758 | await _.sleep(1000);
759 | console.log('This logs after 1 second');
760 | ```
761 |
762 | ### retry
763 |
764 | Attempts to execute a function multiple times until it succeeds.
765 |
766 | ``` js
767 | // Retry fetching data up to 3 times
768 | const data = await _.retry(async () => {
769 | const response = await fetch('https://api.example.com/data');
770 | if (!response.ok) throw new Error('Failed to fetch');
771 | return response.json();
772 | }, { times: 3 });
773 | ```
774 |
775 | ### map
776 |
777 | Asynchronously maps over an array with concurrency control.
778 |
779 | ``` js
780 | // Process 2 items at a time
781 | const results = await _.map([1, 2, 3, 4, 5], async (num) => {
782 | await _.sleep(100);
783 | return num * 2;
784 | }, { concurrency: 2 });
785 | // => [2, 4, 6, 8, 10]
786 | ```
787 |
788 | ### filter
789 |
790 | Asynchronously filters an array with concurrency control.
791 |
792 | ``` js
793 | // Keep only even numbers, processing 3 at a time
794 | const evens = await _.filter([1, 2, 3, 4, 5, 6], async (num) => {
795 | await _.sleep(100);
796 | return num % 2 === 0;
797 | }, { concurrency: 3 });
798 | // => [2, 4, 6]
799 | ```
--------------------------------------------------------------------------------
/docs/zh/Readme.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar: false
3 | ---
4 |
5 | # midash
6 |
7 | 与 lodash 拥有相似 API,基于 ES6+,体积更小的工具函数库,附带额外的异步工具函数。
8 |
9 | + 🔨 高频使用 API
10 | + 🕒 熟悉的 lodash API
11 | + 💪 支持 Tree Shaking
12 | + 👫 支持 Typescript
13 | + 🔥 体积更小 (基于 ES6+)
14 | + 📦 仅 2.5kb 的迷你库
15 | + 🚀 提供异步工具函数
16 |
17 | ## 安装
18 |
19 | ``` bash
20 | # yarn
21 | $ yarn add midash
22 | # pnpm
23 | $ pnpm i midash
24 | ```
25 |
26 | ``` js
27 | import { sum } from 'midash'
28 |
29 | sum([1, 3, 5, 7, 9])
30 | ```
31 |
32 | ## 异步工具函数
33 |
34 | midash 提供了几个有用的异步工具函数:
35 |
36 | - `sleep(ms)`: 暂停执行指定的毫秒数。
37 | - `retry(fn, options)`: 多次尝试执行函数,直到成功。
38 | - `map(iterable, mapper, options)`: 异步映射可迭代对象,可控制并发数。
39 | - `filter(iterable, filterer, options)`: 异步过滤可迭代对象,可控制并发数。
40 |
41 | 使用示例:
42 |
43 | ```js
44 | import { sleep, retry, map, filter } from 'midash'
45 |
46 | // 暂停1秒
47 | await sleep(1000)
48 |
49 | // 最多尝试3次
50 | const result = await retry(async () => {
51 | // 你的异步操作
52 | }, { times: 3 })
53 |
54 | // 异步映射数组,并发数为2
55 | const mappedResults = await map([1, 2, 3, 4], async (num) => {
56 | await sleep(100)
57 | return num * 2
58 | }, { concurrency: 2 })
59 |
60 | // 异步过滤数组
61 | const filteredResults = await filter([1, 2, 3, 4, 5], async (num) => {
62 | await sleep(100)
63 | return num % 2 === 0
64 | })
65 | ```
--------------------------------------------------------------------------------
/docs/zh/api.md:
--------------------------------------------------------------------------------
1 | # midash API
2 |
3 | ## Object
4 |
5 | ### get
6 |
7 | 获取 `object` 的嵌套属性。
8 |
9 | ``` js
10 | const object = { a: [{ b: 3 }] }
11 |
12 | // => 3
13 | _.get(object, 'a[0].b')
14 |
15 | // => 3
16 | _.get(object, ['a', '0', 'b'])
17 |
18 | // => 'default'
19 | _.get(object, 'a.b.c', 'default')
20 | ```
21 |
22 | ### omit
23 |
24 | 忽略 `object` 的某些属性,并返回新的 `object`。
25 |
26 | ::: warning
27 | 在 `midash` 中无法使用 `_.omit(object, 'a', 'b')`,请使用 `_.omit(object, ['a', 'b'])` 替代。
28 | :::
29 |
30 | ``` js
31 | const object = {
32 | a: 3,
33 | b: 4,
34 | c: 5
35 | }
36 |
37 | //=> { c: 5 }
38 | _.omit(object, ['a', 'b'])
39 |
40 | ```
41 |
42 | ### omitBy
43 |
44 | 通过函数忽略 `object` 的某些属性,并返回新的 `object`。
45 |
46 | ``` js
47 | const object = {
48 | a: 3,
49 | b: 4,
50 | c: 5
51 | }
52 |
53 | // 通过 value 进行筛选忽略
54 | //=> { b:4, c: 5 }
55 | _.omitBy(object, value => value === 3)
56 |
57 | // 通过 key 进行筛选忽略
58 | //=> { b:4, c: 5 }
59 | _.omitBy(object, (value, key) => key === 'a')
60 | ```
61 |
62 | ### pick
63 |
64 | 选择 `object` 的某些属性,并返回新的 `object`。
65 |
66 | ::: warning
67 | 在 `midash` 中无法使用 `_.pick(object, 'a', 'b')`,请使用 `_.pick(object, ['a', 'b'])` 替代。
68 | :::
69 |
70 | ``` js
71 | const object = {
72 | a: 3,
73 | b: 4,
74 | c: undefined
75 | }
76 |
77 | //=> { a: 3, b: 4 }
78 | _.pick(object, ['a', 'b'])
79 |
80 | //=> {}
81 | _.pick(object, ['z'])
82 |
83 | //=> { c: undefined }
84 | _.pick(object, ['c'])
85 | ```
86 |
87 | ### pickBy
88 |
89 | 通过函数选择 `object` 的某些属性,并返回新的 `object`。
90 |
91 | ``` js
92 | const object = {
93 | a: 3,
94 | b: 4,
95 | }
96 |
97 | //=> { a: 3 }
98 | _.pickBy(object, value => value === 3)
99 |
100 | //=> { a: 3 }
101 | _.pickBy(object, (value, key) => key === 'a')
102 | ```
103 |
104 | ### defaults
105 |
106 | 将来源对象中的所有可枚举属性分配到目标对象上,但仅在目标对象的属性为 undefined 时才分配。
107 |
108 | ``` js
109 | //=> { mode: 'development', sourcemap: true, devtool: true }
110 | _.defaults({
111 | mode: 'development',
112 | sourcemap: true
113 | }, {
114 | mode: 'production',
115 | devtool: true
116 | })
117 | ```
118 |
119 | ### clone
120 |
121 | 浅拷贝对象属性
122 |
123 | ``` js
124 | const o = { a: { aa: 3 }, b: 4 }
125 |
126 | //=> true
127 | _.clone(o).a === o.a
128 | ```
129 |
130 | ### cloneDeep
131 |
132 | 递归拷贝对象属性
133 |
134 | ``` js
135 | const o = { a: { aa: 3 }, b: 4 }
136 |
137 | //=> false
138 | _.cloneDeep(o).a === o.a
139 | ```
140 |
141 | ### merge
142 |
143 | 递归合并所有对象的属性至第一参数对象中,并返回新的对象。
144 |
145 | ``` js
146 | //=> { a: 4, b: 2 }
147 | _.merge({ a: 1 }, { b: 2 }, { a: 3 }, { a: 4 })
148 | ```
149 |
150 | ### assign
151 |
152 | 分配来源对象的可枚举属性到目标对象上。 来源对象的应用规则是从左到右,随后的下一个对象的属性会覆盖上一个对象的属性。
153 |
154 | ``` js
155 | // => { a: 1, b: 3, c: 5, d: 6 }
156 | _.assign({ a: 1, b: 2 }, { b: 3, c: 4 }, { c: 5, d: 6 })
157 | ```
158 |
159 | ### mapKeys
160 |
161 | 使用函数转换对象的键,并返回新对象。
162 |
163 | ``` js
164 | //=> { a3: 3, b4: 4 }
165 | _.mapKeys({ a: 3, b: 4 }, (v, k) => `${k}${v}`)
166 | ```
167 |
168 | ## Array
169 |
170 | ### chunk
171 |
172 | 对数组按照 `size` 进行分组。
173 |
174 | ``` js
175 | //=> [[0, 1, 2], [3, 4, 5]]
176 | _.chunk([0, 1, 2, 3, 4, 5], 3)
177 |
178 | //=> [[0], [1], [2]]
179 | _.chunk([0, 1, 2])
180 |
181 | //=> [['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']]
182 | _.chunk('abcdefghi', 3)
183 | ```
184 |
185 | ### sample
186 |
187 | 从一个数组中随机获取值。
188 |
189 | ``` js
190 | // get a random element from [0, 3, 6, 10]
191 | _.sample([0, 3, 6, 10])
192 |
193 | //=> undefined
194 | _.sample([])
195 | ```
196 |
197 | ### sampleSize
198 |
199 | 从一个数组中随机获取 `n` 个值。
200 |
201 | ``` js
202 | //=> Maybe [1, 2]
203 | _.sampleSize([1, 2, 3], 2)
204 |
205 | //=> [1, 2, 3]
206 | _.sampleSize([1, 2, 3], 4)
207 | ```
208 |
209 | ### shuffle
210 |
211 | 创建一个被打乱元素顺序的数组。
212 |
213 | ``` js
214 | //=> [2, 3, 1](随机顺序)
215 | _.shuffle([1, 2, 3])
216 | ```
217 |
218 | ### difference/differenceBy
219 |
220 | 创建一个不包含在其他给定数组中的数组值的新数组。
221 |
222 | ::: tip
223 | 在 `midash` 中,`differenceBy` 是 `difference` 的别名。
224 | :::
225 |
226 | ``` js
227 | //=> [2, 4]
228 | _.difference([1, 2, 3, 4], [1, 3, 5])
229 |
230 | //=> [{ a: 4 }]
231 | _.differenceBy([{ a: 3 }, { a: 4 }], [{ a: 3 }], x => x.a)
232 | ```
233 |
234 | ### intersection
235 |
236 | 创建一个包含所有给定数组中共有元素的新数组。
237 |
238 | ``` js
239 | //=> [2]
240 | _.intersection([1, 2], [2, 3])
241 |
242 | //=> [{ id: 1 }]
243 | _.intersection([{ id: 1 }, { id: 2 }], [{ id: 1 }, { id: 3 }], item => item.id)
244 | ```
245 |
246 | ### uniq
247 |
248 | 创建一个去重后的数组。
249 |
250 | ``` js
251 | //=> [1, 2, 3]
252 | _.uniq([1, 2, 3, 1, 2])
253 | ```
254 |
255 | ### uniqBy
256 |
257 | 使用迭代函数的返回值来进行去重。
258 |
259 | ``` js
260 | //=> [{ id: 1 }, { id: 2 }]
261 | _.uniqBy([{ id: 1 }, { id: 2 }, { id: 1 }], item => item.id)
262 | ```
263 |
264 | ### keyBy
265 |
266 | 根据条件生成键,并对数组转化为对象。
267 |
268 | ``` js
269 | const list = [
270 | { id: 1, name: 'hello' },
271 | { id: 2, name: 'world' },
272 | ]
273 |
274 | //=> { '1': { id: 1, name: 'hello' }, '2': { id: 2, name: 'world' } }
275 | _.keyBy(list, x => x.id)
276 | ```
277 |
278 | ### groupBy
279 |
280 | 根据条件对数组进行分组。
281 |
282 | ``` js
283 | //=> { '3': ['one', 'two'], '5': ['three'] }
284 | _.groupBy(['one', 'two', 'three'], x => x.length)
285 | ```
286 |
287 | ### zip
288 |
289 | 创建一个分组元素的数组,数组的第一个元素包含所有给定数组的第一个元素,数组的第二个元素包含所有给定数组的第二个元素,以此类推。
290 |
291 | ``` js
292 | // => [[1, 'a', true],[2, 'b', false],[3, 'c', undefined]];
293 | _.zip([1, 2, 3], ['a', 'b', 'c'], [true, false]);
294 |
295 | // => [[undefined, 1], [undefined, 2], [undefined, 3]];
296 | _.zip([],[1, 2, 3])
297 |
298 | // => [[1, 'a', undefined], [2, 'b', undefined],[undefined, 'c', undefined]];
299 | _.zip([1, 2], ['a', 'b', 'c'], [])
300 | ```
301 |
302 | ### unzip
303 |
304 | 这个方法类似于_.zip,除了它接收分组元素的数组,并且创建一个数组,分组元素到打包前的结构。
305 |
306 | ``` js
307 | // => [[1, 2, 3], ['a', 'b', 'c'], [true, false, true]]
308 | _.unzip([[1, 'a', true], [2, 'b', false], [3, 'c', true]])
309 |
310 | // => []
311 | _.unzip([])
312 |
313 | // => [[1, 2, 4], ['a', 'b', 'c'], [undefined, 3, undefined]]
314 | _.unzip([[1, 'a'], [2, 'b', 3], [4, 'c']])
315 | ```
316 |
317 | ### compact
318 |
319 | 过滤掉数组中的假值( `false`、`undefined`、`null`、`''`、`0`), 并返回新数组。
320 |
321 | ``` js
322 | // => [1, 2, 3]
323 | _.compact([0, 1, false, 2, '', 3])
324 | ```
325 |
326 | ### head
327 |
328 | 获取数组中的第一个元素。
329 |
330 | ``` js
331 | // => 1
332 | _.head([1, 2, 3])
333 |
334 | // => undefined
335 | _.head([])
336 | ```
337 |
338 | ### nth
339 |
340 | 获取数组中指定索引的元素。如果索引为负数,则从末尾开始计数。
341 |
342 | ``` js
343 | // => 2
344 | _.nth([1, 2, 3], 1)
345 |
346 | // => 3
347 | _.nth([1, 2, 3], -1)
348 | ```
349 |
350 | ## String
351 |
352 | ### camelCase
353 |
354 | 将字符串转换为驼峰式。
355 |
356 | ``` js
357 | // => 'fooBar'
358 | _.camelCase('foo_bar')
359 |
360 | // => 'fooBar'
361 | _.camelCase('foo-bar')
362 |
363 | // => 'fooBar'
364 | _.camelCase('Foo Bar')
365 | ```
366 |
367 | ### snakeCase
368 |
369 | 将字符串转换为下划线式。
370 |
371 | ``` js
372 | // => 'foo_bar'
373 | _.snakeCase('fooBar')
374 |
375 | // => 'foo_bar'
376 | _.snakeCase('foo-bar')
377 |
378 | // => 'foo_bar'
379 | _.snakeCase('Foo Bar')
380 | ```
381 |
382 | ### kebabCase
383 |
384 | 将字符串转换为短横线连接式。
385 |
386 | ``` js
387 | // => 'foo-bar'
388 | _.kebabCase('fooBar')
389 |
390 | // => 'foo-bar'
391 | _.kebabCase('foo_bar')
392 |
393 | // => 'foo-bar'
394 | _.kebabCase('Foo Bar')
395 | ```
396 |
397 | ### words
398 |
399 | 将字符串分割成单词数组。
400 |
401 | ``` js
402 | // => ['foo', 'bar']
403 | _.words('foo bar')
404 |
405 | // => ['foo', 'bar']
406 | _.words('foo-bar', /[^, -]+/g)
407 | ```
408 |
409 | ### template
410 |
411 | 创建一个预编译模板函数,可以插入数据属性。
412 |
413 | ``` js
414 | // => 'Hello Fred!'
415 | _.template('Hello ${name}!')({ name: 'Fred' })
416 | ```
417 |
418 | ## Number
419 |
420 | ### random
421 |
422 | 获取一个随机整数。
423 |
424 | ``` js
425 | // 10 到 20 之间的一个随机整数,闭区间,包括 10 与 20
426 | _.random(10, 20)
427 |
428 | // 0 到 20 之间的一个随机整数
429 | _.random(20)
430 |
431 | // 0 到 1 之间的一个随机整数
432 | _.random()
433 | ```
434 |
435 | ### range
436 |
437 | 创建一个包含从 start 到 end(不包括end)之间步长为 step 的数字的数组。
438 |
439 | ``` js
440 | //=> [0, 1, 2, 3]
441 | _.range(4)
442 |
443 | //=> [0, -1, -2, -3]
444 | _.range(-4)
445 |
446 | //=> [1, 2, 3, 4]
447 | _.range(1, 5)
448 |
449 | //=> [5, 4, 3, 2]
450 | _.range(5, 1)
451 |
452 | //=> [0, -1, -2, -3]
453 | _.range(0, -4, -1)
454 | ```
455 |
456 | ## Lang
457 |
458 | ### castArray
459 |
460 | 如果 value 不是数组, 那么强制转为数组。
461 |
462 | ``` js
463 | _.castArray(1);
464 | // => [1]
465 |
466 | _.castArray({ 'a': 1 });
467 | // => [{ 'a': 1 }]
468 |
469 | _.castArray('abc');
470 | // => ['abc']
471 |
472 | _.castArray(null);
473 | // => [null]
474 |
475 | _.castArray(undefined);
476 | // => [undefined]
477 |
478 | _.castArray();
479 | // => []
480 |
481 | const array = [1, 2, 3];
482 | console.log(_.castArray(array) === array);
483 | // => true
484 | ```
485 |
486 | ### isArray
487 |
488 | 检查值是否为 Array 类型。
489 |
490 | ``` js
491 | //=> true
492 | _.isArray([])
493 |
494 | //=> false
495 | _.isArray({})
496 | ```
497 |
498 | ### isBoolean
499 |
500 | 检查值是否为布尔原始类型。
501 |
502 | ``` js
503 | //=> true
504 | _.isBoolean(false)
505 |
506 | //=> true
507 | _.isBoolean(true)
508 |
509 | //=> false
510 | _.isBoolean(null)
511 | ```
512 |
513 | ### isObject
514 |
515 | 检查值是否为 Object 类型。
516 |
517 | ``` js
518 | //=> true
519 | _.isObject({})
520 |
521 | //=> true
522 | _.isObject([])
523 |
524 | //=> true
525 | _.isObject(x => {})
526 |
527 | //=> false
528 | _.isObject(null)
529 | ```
530 |
531 | ### isPlainObject
532 |
533 | 检查值是否为普通对象。
534 |
535 | ``` js
536 | //=> true
537 | _.isPlainObject({})
538 |
539 | //=> true
540 | _.isPlainObject(Object.create(null))
541 |
542 | //=> false
543 | _.isPlainObject([])
544 |
545 | //=> false
546 | _.isPlainObject(new Date())
547 | ```
548 |
549 | ### isPromise
550 |
551 | 检查值是否为 Promise 对象。
552 |
553 | ``` js
554 | //=> true
555 | _.isPromise(Promise.resolve())
556 |
557 | //=> false
558 | _.isPromise({})
559 | ```
560 |
561 | ### isPrimitive
562 |
563 | 检查值是否为原始类型。
564 |
565 | ``` js
566 | //=> true
567 | _.isPrimitive(null)
568 |
569 | //=> true
570 | _.isPrimitive(undefined)
571 |
572 | //=> true
573 | _.isPrimitive(1)
574 |
575 | //=> true
576 | _.isPrimitive('string')
577 |
578 | //=> true
579 | _.isPrimitive(true)
580 |
581 | //=> true
582 | _.isPrimitive(Symbol())
583 |
584 | //=> false
585 | _.isPrimitive({})
586 |
587 | //=> false
588 | _.isPrimitive([])
589 | ```
590 |
591 | ### isTypedArray
592 |
593 | 检查值是否为类型化数组。
594 |
595 | ``` js
596 | //=> true
597 | _.isTypedArray(new Uint8Array([1, 2, 3]))
598 |
599 | //=> false
600 | _.isTypedArray([])
601 | ```
602 |
603 | ### isEqual
604 |
605 | 执行深度比较两个值是否相等。
606 |
607 | ``` js
608 | //=> true
609 | _.isEqual([1, 2, 3], [1, 2, 3])
610 |
611 | //=> false
612 | _.isEqual([1, 2, 3], [1, 2, 4])
613 |
614 | //=> true
615 | _.isEqual({ a: 1, b: 2 }, { b: 2, a: 1 })
616 | ```
617 |
618 | ## Function
619 |
620 | ### compose/flowRight
621 |
622 | 从右至左执行函数,并将上一个函数的返回值作为下一个函数的参数。
623 |
624 | ::: tip
625 | 在 `midash` 中,`flowRight` 是 `compose` 的别名。
626 | :::
627 |
628 | ``` js
629 | const double = x => x * 2
630 | const square = x => x * x
631 |
632 | //=> 200
633 | _.compose(double, square)(10)
634 | _.flowRight(double, square)(10)
635 | ```
636 |
637 | ### property
638 |
639 | 创建一个返回给定对象的 path 的值的函数。
640 |
641 | ``` js
642 | const objects = [
643 | { 'a': { 'b': 2 } },
644 | { 'a': { 'b': 1 } }
645 | ];
646 | // => [2, 1]
647 | _.map(objects, _.property('a.b'));
648 |
649 | // => [1, 2]
650 | _.map(_.sortBy(objects, _.property(['a', 'b'])), 'a.b');
651 | ```
652 |
653 | ### once
654 |
655 | 创建一个只能调用一次的函数。重复调用将返回第一次调用的结果。
656 |
657 | ``` js
658 | // `initialize` 只能调用 `createApplication` 一次。
659 | const initialize = _.once(createApplication);
660 | initialize();
661 | initialize(); // 无效
662 | ```
663 |
664 | ### memoize
665 |
666 | 创建一个会缓存 func 结果的函数。
667 |
668 | ``` js
669 | const object = { 'a': 1, 'b': 2 };
670 | const other = { 'c': 3, 'd': 4 };
671 |
672 | // => [1, 2]
673 | const values = _.memoize(Object.values);
674 | values(object);
675 |
676 | // => [3, 4]
677 | values(other);
678 |
679 | object.a = 2;
680 | // => [1, 2] (缓存结果)
681 | values(object);
682 | ```
683 |
684 | ### debounce
685 |
686 | 创建一个防抖函数,该函数会在调用结束后的指定毫秒后才执行。
687 |
688 | ``` js
689 | // 创建一个防抖函数,只有在上次调用后至少200ms才会执行updateChart
690 | const debouncedUpdate = _.debounce(updateChart, 200);
691 |
692 | // 多次调用
693 | window.addEventListener('resize', debouncedUpdate);
694 | ```
695 |
696 | ### throttle
697 |
698 | 创建一个节流函数,该函数最多每隔 wait 毫秒执行一次。
699 |
700 | ``` js
701 | // 创建一个节流函数,每500ms最多执行saveInput一次
702 | const throttledSave = _.throttle(saveInput, 500);
703 |
704 | // 多次调用
705 | inputField.addEventListener('input', throttledSave);
706 | ```
707 |
708 | ## Math
709 |
710 | ### sum
711 |
712 | 计算数组中所有值的总和。
713 |
714 | ``` js
715 | // => 6
716 | _.sum([1, 2, 3])
717 |
718 | // => 0
719 | _.sum([])
720 | ```
721 |
722 | ### max
723 |
724 | 获取数字数组中最大的值。如果数组为空或者不存在,则返回 `undefined`。
725 |
726 | ::: tip
727 | 在 `midash` 中,`maxBy` 是 `max` 的别名。
728 | :::
729 |
730 | ``` js
731 | // => 5
732 | _.max([-5, -3, 0, 3, 5])
733 |
734 | // => { a: 4 }
735 | _.maxBy([
736 | { a: 3 },
737 | { a: 4 }
738 | ], x => x.a)
739 | ```
740 |
741 | ### min
742 |
743 | 获取数字数组中最小的值。如果数组为空或者不存在,则返回 `undefined`。
744 |
745 | ::: tip
746 | 在 `midash` 中,`minBy` 是 `min` 的别名。
747 | :::
748 |
749 | ``` js
750 | // => -5
751 | _.min([-5, -3, 0, 3, 5])
752 |
753 | // => { a: 3 }
754 | _.minBy([
755 | { a: 3 },
756 | { a: 4 }
757 | ], x => x.a)
758 | ```
759 |
760 | ## Async
761 |
762 | ### sleep
763 |
764 | 创建一个在指定毫秒后解析的 Promise。
765 |
766 | ``` js
767 | // 暂停执行1秒
768 | await _.sleep(1000);
769 | console.log('1秒后输出');
770 | ```
771 |
772 | ### retry
773 |
774 | 尝试多次执行一个函数,直到成功。
775 |
776 | ``` js
777 | // 最多尝试3次获取数据
778 | const data = await _.retry(async () => {
779 | const response = await fetch('https://api.example.com/data');
780 | if (!response.ok) throw new Error('获取失败');
781 | return response.json();
782 | }, { times: 3 });
783 | ```
784 |
785 | ### map
786 |
787 | 异步映射数组,可控制并发数量。
788 |
789 | ``` js
790 | // 一次处理2个项目
791 | const results = await _.map([1, 2, 3, 4, 5], async (num) => {
792 | await _.sleep(100);
793 | return num * 2;
794 | }, { concurrency: 2 });
795 | // => [2, 4, 6, 8, 10]
796 | ```
797 |
798 | ### filter
799 |
800 | 异步过滤数组,可控制并发数量。
801 |
802 | ``` js
803 | // 仅保留偶数,一次处理3个项目
804 | const evens = await _.filter([1, 2, 3, 4, 5, 6], async (num) => {
805 | await _.sleep(100);
806 | return num % 2 === 0;
807 | }, { concurrency: 3 });
808 | // => [2, 4, 6]
809 | ```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "midash",
3 | "description": "An alternative to lodash with the same API.",
4 | "version": "1.0.3",
5 | "sideEffects": false,
6 | "license": "MIT",
7 | "exports": {
8 | ".": {
9 | "require": {
10 | "types": "./dist/index.d.ts",
11 | "default": "./dist/index.cjs.js"
12 | },
13 | "import": {
14 | "types": "./dist/index.d.mts",
15 | "default": "./dist/index.esm.js"
16 | }
17 | }
18 | },
19 | "main": "./dist/index.cjs.js",
20 | "module": "./dist/index.esm.js",
21 | "typings": "dist/index.d.ts",
22 | "files": [
23 | "dist",
24 | "src"
25 | ],
26 | "engines": {
27 | "node": ">=14"
28 | },
29 | "scripts": {
30 | "build": "tsup",
31 | "test": "vitest",
32 | "lint": "eslint src/**/*.ts",
33 | "prepare": "pnpm run build",
34 | "size": "size-limit",
35 | "analyze": "size-limit --why",
36 | "release": "release-it",
37 | "docs:dev": "vuepress dev docs",
38 | "docs:build": "vuepress build docs"
39 | },
40 | "husky": {
41 | "hooks": {
42 | "pre-commit": "pnpm lint"
43 | }
44 | },
45 | "prettier": {
46 | "printWidth": 80,
47 | "semi": false,
48 | "singleQuote": true,
49 | "trailingComma": "es5",
50 | "arrowParens": "avoid"
51 | },
52 | "eslintConfig": {
53 | "extends": [
54 | "prettier",
55 | "plugin:prettier/recommended"
56 | ],
57 | "rules": {
58 | "dot-notation": "off"
59 | },
60 | "parser": "@typescript-eslint/parser",
61 | "parserOptions": {
62 | "ecmaVersion": "latest",
63 | "sourceType": "module"
64 | }
65 | },
66 | "author": "shfshanyue",
67 | "repository": "shfshanyue/midash",
68 | "size-limit": [
69 | {
70 | "path": "dist/index.cjs.js",
71 | "limit": "20 KB"
72 | },
73 | {
74 | "path": "dist/index.esm.js",
75 | "limit": "20 KB"
76 | }
77 | ],
78 | "devDependencies": {
79 | "@types/node": "^20.8.10",
80 | "@typescript-eslint/parser": "^6.9.1",
81 | "@vitest/coverage-v8": "^0.34.6",
82 | "@vuepress/client": "2.0.0-beta.66",
83 | "@vuepress/theme-default": "2.0.0-beta.66",
84 | "@vuepress/utils": "2.0.0-beta.66",
85 | "eslint": "^8.52.0",
86 | "eslint-config-prettier": "^9.0.0",
87 | "eslint-plugin-prettier": "^5.0.1",
88 | "husky": "^8.0.3",
89 | "microbundle": "^0.15.1",
90 | "release-it": "^16.2.1",
91 | "size-limit": "^10.0.2",
92 | "time-span": "^5.1.0",
93 | "tslib": "^2.6.2",
94 | "tsup": "^7.2.0",
95 | "typescript": "^5.2.2",
96 | "vitest": "^0.34.6",
97 | "vue": "^3.3.7",
98 | "vuepress": "2.0.0-beta.66"
99 | },
100 | "release-it": {
101 | "git": {
102 | "push": true,
103 | "commit": true,
104 | "tag": true,
105 | "requireCommits": false,
106 | "requireCleanWorkingDir": false
107 | },
108 | "github": {
109 | "release": true,
110 | "draft": true
111 | },
112 | "npm": {
113 | "publish": true,
114 | "ignoreVersion": false
115 | },
116 | "hooks": {
117 | "before:init": "CI=true pnpm test && pnpm run build",
118 | "after:release": "echo Successfully released ${name} v${version} to ${repo.repository}."
119 | }
120 | },
121 | "keywords": [
122 | "utils",
123 | "utilities",
124 | "toolkit",
125 | "pure",
126 | "functional"
127 | ],
128 | "packageManager": "pnpm@8.5.1"
129 | }
130 |
--------------------------------------------------------------------------------
/src/assign.ts:
--------------------------------------------------------------------------------
1 | export function assign(target: T, source: U): T & U
2 | export function assign(
3 | target: T,
4 | source1: U,
5 | source2: V
6 | ): T & U & V
7 | export function assign(
8 | target: T,
9 | source1: U,
10 | source2: V,
11 | source3: W
12 | ): T & U & V & W
13 |
14 | export function assign(target: object, ...sources: any[]): any {
15 | if (target === null || target === undefined) {
16 | target = {}
17 | }
18 | return Object.assign(target, ...sources)
19 | }
20 |
--------------------------------------------------------------------------------
/src/async.ts:
--------------------------------------------------------------------------------
1 | export function sleep(ms: number) {
2 | return new Promise(resolve => {
3 | setTimeout(resolve, ms)
4 | })
5 | }
6 |
7 | interface RetryOptions {
8 | readonly times?: number
9 | readonly onFailedAttempt?: (error: Error) => void | Promise
10 | }
11 |
12 | export class AbortError extends Error {
13 | originalError: Error
14 |
15 | constructor(message: string | Error) {
16 | super()
17 |
18 | if (message instanceof Error) {
19 | this.originalError = message
20 | ;({ message } = message)
21 | } else {
22 | this.originalError = new Error(message)
23 | this.originalError.stack = this.stack
24 | }
25 |
26 | this.name = 'AbortError'
27 | this.message = message
28 | }
29 | }
30 |
31 | export async function retry(
32 | run: (attemptCount: number) => Promise | T,
33 | { times = 10, onFailedAttempt = () => {} }: RetryOptions = {}
34 | ) {
35 | let count = 1
36 | async function exec(): Promise {
37 | try {
38 | const result = await run(count)
39 | return result
40 | } catch (e) {
41 | if (count >= times || e instanceof AbortError) {
42 | throw e
43 | }
44 | count++
45 | await onFailedAttempt(e as Error)
46 | return exec()
47 | }
48 | }
49 | return exec()
50 | }
51 |
52 | interface MapOptions {
53 | readonly concurrency?: number
54 | readonly settled?: boolean
55 | }
56 |
57 | export type Mapper = (
58 | element: Element,
59 | index: number
60 | ) => NewElement | Promise
61 |
62 | export function map(
63 | it: Iterable,
64 | mapper: Mapper,
65 | { concurrency = Infinity }: MapOptions = {}
66 | ): Promise {
67 | const list = Array.from(it)
68 | return new Promise(resolve => {
69 | let currentIndex = 0
70 | let result: NewElement[] = []
71 | let resolveCount = 0
72 | let len = list.length
73 | function next() {
74 | const index = currentIndex
75 | currentIndex++
76 | Promise.resolve(list[index])
77 | .then(o => mapper(o, index))
78 | .then(o => {
79 | result[index] = o
80 | resolveCount++
81 | if (resolveCount === len) {
82 | resolve(result)
83 | }
84 | if (currentIndex < len) {
85 | next()
86 | }
87 | })
88 | }
89 | for (let i = 0; i < concurrency && i < len; i++) {
90 | next()
91 | }
92 | })
93 | }
94 |
95 | export async function filter(
96 | it: Iterable,
97 | filterer: (item: Element, index: number) => boolean | Promise,
98 | options?: MapOptions
99 | ): Promise {
100 | const list = await map(
101 | it,
102 | async (item, index) => {
103 | const bool = await filterer(item, index)
104 | return [item, bool]
105 | },
106 | options
107 | )
108 | return list.filter(([_, bool]) => bool).map(([item]) => item)
109 | }
110 |
--------------------------------------------------------------------------------
/src/camelCase.ts:
--------------------------------------------------------------------------------
1 | import { words } from './words'
2 |
3 | export function camelCase(str: string) {
4 | return words(str)
5 | .map((word, i) => (i ? word[0].toUpperCase() + word.slice(1) : word))
6 | .join('')
7 | }
8 |
--------------------------------------------------------------------------------
/src/castArray.ts:
--------------------------------------------------------------------------------
1 | export function castArray(...args: T[]): T[] {
2 | if (!args.length) {
3 | return []
4 | }
5 | const value = args[0]
6 | return Array.isArray(value) ? value : [value]
7 | }
8 |
--------------------------------------------------------------------------------
/src/chunk.ts:
--------------------------------------------------------------------------------
1 | export function chunk(list: ArrayLike, size: number = 1): T[][] {
2 | const l: T[][] = []
3 | for (let i = 0; i < list.length; i++) {
4 | const index = Math.floor(i / Math.floor(size))
5 | l[index] = l[index] || []
6 | l[index].push(list[i])
7 | }
8 | return l
9 | }
10 |
--------------------------------------------------------------------------------
/src/clone.ts:
--------------------------------------------------------------------------------
1 | import { isArray } from './isArray'
2 | import { isPlainObject } from './isPlainObject'
3 | import { isPrimitive } from './isPrimitive'
4 |
5 | export function clone(obj: T): T {
6 | if (isPrimitive(obj)) {
7 | return obj
8 | }
9 | if (isArray(obj)) {
10 | return [...(obj as any)] as T
11 | }
12 | if (isPlainObject(obj)) {
13 | return {
14 | ...obj,
15 | }
16 | }
17 | // TODO: Date/Buffer/Regexp
18 | return obj
19 | }
20 |
21 | export function cloneDeep(obj: T): T {
22 | if (!isArray(obj) && !isPlainObject(obj)) {
23 | return clone(obj)
24 | }
25 | if (isArray(obj)) {
26 | return (obj as any).map((x: any) => cloneDeep(x))
27 | }
28 | return Object.keys(obj).reduce((acc, key) => {
29 | let value = (obj as any)[key]
30 | if (!isPrimitive(value)) {
31 | value = cloneDeep(value)
32 | }
33 | return {
34 | ...acc,
35 | [key]: value,
36 | }
37 | }, {} as T)
38 | }
39 |
--------------------------------------------------------------------------------
/src/compact.ts:
--------------------------------------------------------------------------------
1 | export type FalselyValue = false | 0 | '' | null | undefined
2 |
3 | export const compact = (arr: (T | FalselyValue)[]) => {
4 | return arr.filter((item): item is T => !!item)
5 | }
6 |
--------------------------------------------------------------------------------
/src/compose.ts:
--------------------------------------------------------------------------------
1 | export function compose(...funcs: Function[]): (...args: any[]) => T
2 |
3 | export function compose(...funcs: Function[]) {
4 | return funcs.reduce(
5 | (a, b) =>
6 | (...args: any) =>
7 | a(b(...args)),
8 | (x: any) => x
9 | )
10 | }
11 |
12 | export { compose as flowRight }
13 |
--------------------------------------------------------------------------------
/src/debounce.ts:
--------------------------------------------------------------------------------
1 | export function debounce(
2 | f: (...args: T) => any,
3 | wait: number
4 | ) {
5 | let timer: number
6 | return (...args: T) => {
7 | clearTimeout(timer)
8 | timer = setTimeout(() => {
9 | f(...args)
10 | }, wait) as any as number
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/defaults.ts:
--------------------------------------------------------------------------------
1 | export function defaults(
2 | obj: Record,
3 | ...sources: Record[]
4 | ) {
5 | return sources.reduce((obj, source) => {
6 | for (const key of Object.keys(source)) {
7 | if (obj[key] === undefined) {
8 | obj[key] = source[key]
9 | }
10 | }
11 | return obj
12 | }, obj)
13 | }
14 |
--------------------------------------------------------------------------------
/src/difference.ts:
--------------------------------------------------------------------------------
1 | export const difference = (
2 | list: T[],
3 | values: T[] = [],
4 | by: (arg: T) => keyof any = x => x as number
5 | ): T[] => {
6 | if (!list || !list.length) return []
7 | if (!values.length) return [...list]
8 | const map: Record = values.reduce(
9 | (acc, item) => ({
10 | ...acc,
11 | [by(item)]: true,
12 | }),
13 | {}
14 | )
15 | return list.filter(a => !map[by(a)])
16 | }
17 |
18 | export { difference as differenceBy }
19 |
--------------------------------------------------------------------------------
/src/get.ts:
--------------------------------------------------------------------------------
1 | export function get(
2 | object: TObject,
3 | path: TKey | [TKey]
4 | ): TObject[TKey]
5 | export function get(
6 | object: TObject | null | undefined,
7 | path: TKey | [TKey]
8 | ): TObject[TKey] | undefined
9 | export function get<
10 | TObject extends object,
11 | TKey extends keyof TObject,
12 | TDefault,
13 | >(
14 | object: TObject | null | undefined,
15 | path: TKey | [TKey],
16 | defaultValue: TDefault
17 | ): Exclude | TDefault
18 | export function get<
19 | TObject extends object,
20 | TKey1 extends keyof TObject,
21 | TKey2 extends keyof TObject[TKey1],
22 | >(object: TObject, path: [TKey1, TKey2]): TObject[TKey1][TKey2]
23 | export function get<
24 | TObject extends object,
25 | TKey1 extends keyof TObject,
26 | TKey2 extends keyof TObject[TKey1],
27 | >(
28 | object: TObject | null | undefined,
29 | path: [TKey1, TKey2]
30 | ): TObject[TKey1][TKey2] | undefined
31 | export function get<
32 | TObject extends object,
33 | TKey1 extends keyof TObject,
34 | TKey2 extends keyof TObject[TKey1],
35 | TDefault,
36 | >(
37 | object: TObject | null | undefined,
38 | path: [TKey1, TKey2],
39 | defaultValue: TDefault
40 | ): Exclude | TDefault
41 | export function get<
42 | TObject extends object,
43 | TKey1 extends keyof TObject,
44 | TKey2 extends keyof TObject[TKey1],
45 | TKey3 extends keyof TObject[TKey1][TKey2],
46 | >(object: TObject, path: [TKey1, TKey2, TKey3]): TObject[TKey1][TKey2][TKey3]
47 | export function get<
48 | TObject extends object,
49 | TKey1 extends keyof TObject,
50 | TKey2 extends keyof TObject[TKey1],
51 | TKey3 extends keyof TObject[TKey1][TKey2],
52 | >(
53 | object: TObject | null | undefined,
54 | path: [TKey1, TKey2, TKey3]
55 | ): TObject[TKey1][TKey2][TKey3] | undefined
56 | export function get<
57 | TObject extends object,
58 | TKey1 extends keyof TObject,
59 | TKey2 extends keyof TObject[TKey1],
60 | TKey3 extends keyof TObject[TKey1][TKey2],
61 | TDefault,
62 | >(
63 | object: TObject | null | undefined,
64 | path: [TKey1, TKey2, TKey3],
65 | defaultValue: TDefault
66 | ): Exclude | TDefault
67 | export function get<
68 | TObject extends object,
69 | TKey1 extends keyof TObject,
70 | TKey2 extends keyof TObject[TKey1],
71 | TKey3 extends keyof TObject[TKey1][TKey2],
72 | TKey4 extends keyof TObject[TKey1][TKey2][TKey3],
73 | >(
74 | object: TObject,
75 | path: [TKey1, TKey2, TKey3, TKey4]
76 | ): TObject[TKey1][TKey2][TKey3][TKey4]
77 | export function get<
78 | TObject extends object,
79 | TKey1 extends keyof TObject,
80 | TKey2 extends keyof TObject[TKey1],
81 | TKey3 extends keyof TObject[TKey1][TKey2],
82 | TKey4 extends keyof TObject[TKey1][TKey2][TKey3],
83 | >(
84 | object: TObject | null | undefined,
85 | path: [TKey1, TKey2, TKey3, TKey4]
86 | ): TObject[TKey1][TKey2][TKey3][TKey4] | undefined
87 | export function get<
88 | TObject extends object,
89 | TKey1 extends keyof TObject,
90 | TKey2 extends keyof TObject[TKey1],
91 | TKey3 extends keyof TObject[TKey1][TKey2],
92 | TKey4 extends keyof TObject[TKey1][TKey2][TKey3],
93 | TDefault,
94 | >(
95 | object: TObject | null | undefined,
96 | path: [TKey1, TKey2, TKey3, TKey4],
97 | defaultValue: TDefault
98 | ): Exclude | TDefault
99 | export function get(
100 | object: null | undefined,
101 | path: string | number | string[] | number[]
102 | ): undefined
103 | export function get(
104 | object: object,
105 | path: string | string[] | number[],
106 | defaultValue?: any
107 | ): any
108 |
109 | export function get(
110 | obj: Record | null | undefined,
111 | path: string | number | string[] | number[],
112 | defaultValue?: any
113 | ) {
114 | const paths = Array.isArray(path)
115 | ? path
116 | : String(path)
117 | .replace(/\[(\w+)\]/g, '.$1')
118 | .replace(/\["(\w+)"\]/g, '.$1')
119 | .replace(/\['(\w+)'\]/g, '.$1')
120 | .split('.')
121 | let result = obj
122 | for (const p of paths) {
123 | result = result?.[p]
124 | if (result === undefined) {
125 | return defaultValue
126 | }
127 | }
128 | return result
129 | }
130 |
--------------------------------------------------------------------------------
/src/groupBy.ts:
--------------------------------------------------------------------------------
1 | export function groupBy(
2 | list: T[],
3 | by: (value: T) => unknown
4 | ): Record {
5 | if (!list?.length) {
6 | return {}
7 | }
8 | return list.reduce(
9 | (acc, x) => {
10 | const key = String(by(x))
11 | if (acc[key]) {
12 | acc[key].push(x)
13 | } else {
14 | acc[key] = [x]
15 | }
16 | return acc
17 | },
18 | {} as Record
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/src/head.ts:
--------------------------------------------------------------------------------
1 | export function head(list?: T[]): T | undefined {
2 | return list?.[0]
3 | }
4 |
5 | export function tail(list?: T[]): T[] {
6 | return list?.slice(1) ?? []
7 | }
8 |
9 | export function take(list?: T[], n = 1): T[] {
10 | return list?.slice(0, n) ?? []
11 | }
12 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './sample'
2 | export * from './sampleSize'
3 | export * from './shuffle'
4 | export * from './uniq'
5 | export * from './uniqBy'
6 | export * from './omitBy'
7 | export * from './pickBy'
8 | export * from './pick'
9 | export * from './omit'
10 | export * from './groupBy'
11 | export * from './keyBy'
12 | export * from './chunk'
13 | export * from './get'
14 | export * from './camelCase'
15 | export * from './snakeCase'
16 | export * from './kebabCase'
17 | export * from './defaults'
18 | export * from './random'
19 | export * from './merge'
20 | export * from './isArray'
21 | export * from './isBoolean'
22 | export * from './isObject'
23 | export * from './isPromise'
24 | export * from './isPrimitive'
25 | export * from './isTypedArray'
26 | export * from './isPlainObject'
27 | export * from './isEqual'
28 | export * from './range'
29 | export * from './sum'
30 | export * from './max'
31 | export * from './min'
32 | export * from './clone'
33 | export * from './compose'
34 | export * from './difference'
35 | export * from './async'
36 | export * from './nth'
37 | export * from './intersection'
38 | export * from './debounce'
39 | export * from './head'
40 | export * from './compact'
41 | export * from './zip'
42 | export * from './unzip'
43 | export * from './assign'
44 | export * from './mapKeys'
45 | export * from './mapKeys'
46 | export * from './castArray'
47 | export * from './throttle'
48 | export * from './property'
49 | export * from './once'
50 | export * from './memoize'
51 | export * from './template'
52 |
--------------------------------------------------------------------------------
/src/intersection.ts:
--------------------------------------------------------------------------------
1 | type by = (item: T) => number | string
2 |
3 | export function intersection(...args: Array>): T[] {
4 | if (!args || !args.length) return []
5 | if (args.slice(0, args.length - 1).find(item => typeof item === 'function'))
6 | return []
7 | const list: T[][] = args.filter(Array.isArray)
8 | const by: by =
9 | typeof args[args.length - 1] === 'function'
10 | ? (args[args.length - 1] as by)
11 | : x => x as number
12 | return list?.reduce((a, b) => {
13 | const setB = new Set(b.map(by))
14 | return a.filter(c => setB.has(by(c)))
15 | })
16 | }
17 |
18 | export { intersection as intersectionBy }
19 |
--------------------------------------------------------------------------------
/src/isArray.ts:
--------------------------------------------------------------------------------
1 | export function isArray(value?: any): value is any[] {
2 | return Array.isArray(value)
3 | }
4 |
--------------------------------------------------------------------------------
/src/isBoolean.ts:
--------------------------------------------------------------------------------
1 | export function isBoolean(value: any): value is boolean {
2 | return value === false || value === true
3 | }
4 |
--------------------------------------------------------------------------------
/src/isEqual.ts:
--------------------------------------------------------------------------------
1 | import { isTypedArray } from './isTypedArray'
2 |
3 | export function isEqual(value: any, other: any): boolean {
4 | if (value === other) {
5 | return true
6 | }
7 |
8 | if (
9 | !(value && other && typeof value === 'object' && typeof other === 'object')
10 | ) {
11 | // isNaN
12 | return value !== other && other !== other
13 | }
14 |
15 | if (value.constructor !== other.constructor) {
16 | return false
17 | }
18 |
19 | // 因为二者 constructor 相同,因此只需要判断 value 的数据类型
20 | if (Array.isArray(value)) {
21 | if (value.length !== other.length) {
22 | return false
23 | }
24 |
25 | return value.every((x, i) => {
26 | return isEqual(x, other[i])
27 | })
28 | }
29 |
30 | if (isTypedArray(value)) {
31 | if (value.length !== other.length) {
32 | return false
33 | }
34 |
35 | return value.every((x, i) => x === other[i])
36 | }
37 |
38 | if (value.constructor === RegExp) {
39 | return value.source === other.source && value.flags === other.flags
40 | }
41 |
42 | if (value instanceof Map) {
43 | if (value.size !== other.size) {
44 | return false
45 | }
46 | for (const x of value.keys()) {
47 | if (!other.has(x)) {
48 | return false
49 | }
50 | }
51 | for (const [k, v] of Object.entries(value)) {
52 | if (!isEqual(v, other.get(k))) {
53 | return false
54 | }
55 | }
56 | return true
57 | }
58 |
59 | if (value instanceof Set) {
60 | if (value.size !== other.size) {
61 | return false
62 | }
63 | for (const x of value.keys()) {
64 | if (!other.has(x)) {
65 | return false
66 | }
67 | }
68 | return true
69 | }
70 |
71 | const keys = Object.keys(value)
72 | const otherKeys = Object.keys(other)
73 |
74 | if (keys.length !== otherKeys.length) {
75 | return false
76 | }
77 |
78 | for (const key of keys) {
79 | if (!Object.prototype.hasOwnProperty.call(other, key)) {
80 | return false
81 | }
82 | }
83 |
84 | for (const key of keys) {
85 | if (!isEqual(value[key], other[key])) {
86 | return false
87 | }
88 | }
89 |
90 | return true
91 | }
92 |
--------------------------------------------------------------------------------
/src/isObject.ts:
--------------------------------------------------------------------------------
1 | export function isObject(value?: any): value is object {
2 | return (
3 | (typeof value === 'object' || typeof value === 'function') && value !== null
4 | )
5 | }
6 |
--------------------------------------------------------------------------------
/src/isPlainObject.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from './isObject'
2 |
3 | export function isPlainObject(value?: any) {
4 | if (!isObject(value)) {
5 | return false
6 | }
7 |
8 | // if Object.create(null)
9 | if (Object.getPrototypeOf(value) === null) {
10 | return true
11 | }
12 |
13 | let proto = value
14 | while (Object.getPrototypeOf(proto) !== null) {
15 | proto = Object.getPrototypeOf(proto)
16 | }
17 |
18 | return Object.getPrototypeOf(value) === proto
19 | }
20 |
--------------------------------------------------------------------------------
/src/isPrimitive.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from './isObject'
2 |
3 | export function isPrimitive(value?: any) {
4 | return !isObject(value)
5 | }
6 |
--------------------------------------------------------------------------------
/src/isPromise.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from './isObject'
2 |
3 | export function isPromise(value?: any): value is Promise {
4 | return isObject(value) && typeof (value as any).then === 'function'
5 | }
6 |
--------------------------------------------------------------------------------
/src/isTypedArray.ts:
--------------------------------------------------------------------------------
1 | type TypedArray =
2 | | Uint8Array
3 | | Uint8ClampedArray
4 | | Uint16Array
5 | | Uint32Array
6 | | Int8Array
7 | | Int16Array
8 | | Int32Array
9 | | BigUint64Array
10 | | BigInt64Array
11 | | Float32Array
12 | | Float64Array
13 |
14 | export function isTypedArray(
15 | value?: any
16 | ): value is T {
17 | return value.length !== undefined && ArrayBuffer.isView(value)
18 | }
19 |
--------------------------------------------------------------------------------
/src/kebabCase.ts:
--------------------------------------------------------------------------------
1 | import { words } from './words'
2 |
3 | export function kebabCase(str: string) {
4 | return words(str).join('-')
5 | }
6 |
--------------------------------------------------------------------------------
/src/keyBy.ts:
--------------------------------------------------------------------------------
1 | export function keyBy(
2 | list: T[],
3 | by: (value: T) => unknown
4 | ): Record {
5 | if (!list?.length) {
6 | return {}
7 | }
8 | return list.reduce(
9 | (acc, x) => {
10 | const key = String(by(x))
11 | acc[key] = x
12 | return acc
13 | },
14 | {} as Record
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/src/mapKeys.ts:
--------------------------------------------------------------------------------
1 | export function mapKeys<
2 | T,
3 | K extends string | number | symbol,
4 | KNew extends string | number | symbol,
5 | >(
6 | obj: Record,
7 | iteratee: (value: T, key: string) => KNew = value => value as unknown as KNew
8 | ): Record {
9 | return Object.entries(obj).reduce(
10 | (acc, [key, value]) => {
11 | return {
12 | ...acc,
13 | [iteratee(value, key)]: value,
14 | }
15 | },
16 | {} as Record
17 | )
18 | }
19 |
20 | export function mapValues(
21 | obj: Record,
22 | iteratee: (value: T, key: string) => TNew = value => value as unknown as TNew
23 | ): Record {
24 | return Object.entries(obj).reduce(
25 | (acc, [key, value]) => {
26 | return {
27 | ...acc,
28 | [key]: iteratee(value, key),
29 | }
30 | },
31 | {} as Record
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/src/max.ts:
--------------------------------------------------------------------------------
1 | export function max(
2 | list?: T[],
3 | by: (value: T) => number | string = (x: T) => x as number
4 | ): T | undefined {
5 | if (!list || !list.length) {
6 | return
7 | }
8 | return list.reduce((x, y) => (by(x) > by(y) ? x : y))
9 | }
10 |
11 | export { max as maxBy }
12 |
--------------------------------------------------------------------------------
/src/memoize.ts:
--------------------------------------------------------------------------------
1 | export function memoize(fn: (...args: T) => any) {
2 | let cache: { [key: string]: any } = {}
3 | return (...args: T) => {
4 | let key = args[0]
5 | if (cache[key] !== undefined) {
6 | return cache[key]
7 | } else {
8 | let result = fn(...args)
9 | cache[key] = result
10 | return result
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/merge.ts:
--------------------------------------------------------------------------------
1 | import { isPlainObject } from './isPlainObject'
2 | import { isArray } from './isArray'
3 | import { cloneDeep } from './clone'
4 |
5 | const isArrayOrPlainObject = (o?: any) => isPlainObject(o) || isArray(o)
6 |
7 | export function merge(
8 | object: TObject,
9 | source: TSource
10 | ): TObject & TSource
11 | export function merge(
12 | object: TObject,
13 | source1: TSource1,
14 | source2: TSource2
15 | ): TObject & TSource1 & TSource2
16 | export function merge(
17 | object: TObject,
18 | source1: TSource1,
19 | source2: TSource2,
20 | source3: TSource3
21 | ): TObject & TSource1 & TSource2 & TSource3
22 | export function merge(object: any, ...sources: any[]): any {
23 | if (
24 | sources.length === 1 &&
25 | (!isArrayOrPlainObject(sources[0]) || !isArrayOrPlainObject(object))
26 | ) {
27 | if (sources[0] === undefined) {
28 | return cloneDeep(object)
29 | }
30 | return cloneDeep(sources[0])
31 | }
32 | for (const source of sources) {
33 | if (!isArrayOrPlainObject(source)) {
34 | object = merge(object, source)
35 | } else {
36 | for (const key of Object.keys(source)) {
37 | ;(object as any)[key] = merge((object as any)[key], source[key])
38 | }
39 | }
40 | }
41 | return object
42 | }
43 |
--------------------------------------------------------------------------------
/src/min.ts:
--------------------------------------------------------------------------------
1 | export function min(
2 | list?: T[],
3 | by: (value: T) => number | string = (x: T) => x as number
4 | ): T | undefined {
5 | if (!list || !list.length) {
6 | return
7 | }
8 | return list.reduce((x, y) => (by(x) < by(y) ? x : y))
9 | }
10 |
11 | export { min as minBy }
12 |
--------------------------------------------------------------------------------
/src/nth.ts:
--------------------------------------------------------------------------------
1 | export function nth(list: T[], n: number = 0): undefined | T {
2 | return n >= 0 ? list[n] : list[list.length + n]
3 | }
4 |
5 | export { nth as at }
6 |
--------------------------------------------------------------------------------
/src/omit.ts:
--------------------------------------------------------------------------------
1 | export function omit(
2 | object: T,
3 | paths?: K[]
4 | ): Omit
5 |
6 | export function omit(obj: T, paths?: string[]): Partial
7 |
8 | export function omit(obj: object, paths: string[] = []) {
9 | if (!obj) {
10 | return {}
11 | }
12 | return paths.reduce(
13 | (acc, key) => {
14 | delete acc[key]
15 | return acc
16 | },
17 | { ...obj } as any
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/omitBy.ts:
--------------------------------------------------------------------------------
1 | export function omitBy(
2 | obj: T,
3 | predicate: (value: T[keyof T], key: string) => unknown
4 | ): Partial
5 |
6 | export function omitBy(
7 | obj: Record,
8 | predicate: (value: T, key: string) => unknown = () => true
9 | ): Record {
10 | if (!obj) {
11 | return {}
12 | }
13 | return Object.fromEntries(
14 | Object.entries(obj).filter(
15 | ([key, value]) => !Boolean(predicate(value, key))
16 | )
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/src/once.ts:
--------------------------------------------------------------------------------
1 | export function once(fn: (...args: T) => any) {
2 | let called = false
3 | return (...args: T) => {
4 | if (!called) {
5 | called = true
6 | return fn(...args)
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/pick.ts:
--------------------------------------------------------------------------------
1 | export function pick(
2 | object: T,
3 | paths?: K[]
4 | ): Pick
5 |
6 | export function pick(obj: T, paths?: string[]): Partial
7 |
8 | export function pick(obj: object, paths: string[] = []) {
9 | if (!obj) {
10 | return {}
11 | }
12 | return paths.reduce((acc, key) => {
13 | if (Object.prototype.hasOwnProperty.call(obj, key)) {
14 | acc[key] = (obj as any)[key]
15 | }
16 | return acc
17 | }, {} as any)
18 | }
19 |
--------------------------------------------------------------------------------
/src/pickBy.ts:
--------------------------------------------------------------------------------
1 | export function pickBy(
2 | obj: Record,
3 | predicate: (value: T, key: string) => unknown = value => Boolean(value)
4 | ) {
5 | if (!obj) {
6 | return {}
7 | }
8 | return Object.fromEntries(
9 | Object.entries(obj).filter(([key, value]) => Boolean(predicate(value, key)))
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/src/property.ts:
--------------------------------------------------------------------------------
1 | import { get } from './get'
2 |
3 | export const property = (path: string | string[] | number[]) => (obj: any) =>
4 | get(obj, path)
5 |
--------------------------------------------------------------------------------
/src/random.ts:
--------------------------------------------------------------------------------
1 | export function random(lower?: number, upper?: number) {
2 | if (lower === undefined) {
3 | ;[lower, upper] = [0, 1]
4 | }
5 | if (upper === undefined) {
6 | ;[lower, upper] = [0, lower]
7 | }
8 | return Math.floor(Math.random() * (upper - lower + 1)) + lower
9 | }
10 |
--------------------------------------------------------------------------------
/src/range.ts:
--------------------------------------------------------------------------------
1 | export function range(start: number, end?: number, step?: number): number[] {
2 | if (end === undefined) {
3 | end = start
4 | start = 0
5 | }
6 |
7 | if (step === 0) {
8 | return Array(Math.ceil(end - start)).fill(start)
9 | }
10 |
11 | step = step || (start > end ? -1 : 1)
12 |
13 | const r = []
14 | for (let i = start; start > end ? i > end : i < end; i += step) {
15 | r.push(i)
16 | }
17 |
18 | return r
19 | }
20 |
--------------------------------------------------------------------------------
/src/sample.ts:
--------------------------------------------------------------------------------
1 | import { random } from './random'
2 |
3 | export function sample(list: T[] = []) {
4 | const len = list.length
5 | return len ? list[random(len - 1)] : undefined
6 | }
7 |
--------------------------------------------------------------------------------
/src/sampleSize.ts:
--------------------------------------------------------------------------------
1 | import { random } from './random'
2 |
3 | export function sampleSize(list: T[], n: number = 1) {
4 | if (n <= 0 || Number.isNaN(n) || !list?.length) {
5 | return []
6 | }
7 | const result = [...list]
8 | const len = result.length
9 | n = n > len ? len : n
10 | for (let i = len - 1; i >= len - n; i--) {
11 | const rand = random(i)
12 | ;[result[i], result[rand]] = [result[rand], result[i]]
13 | }
14 | return result.slice(-n)
15 | }
16 |
--------------------------------------------------------------------------------
/src/shuffle.ts:
--------------------------------------------------------------------------------
1 | import { sampleSize } from './sampleSize'
2 |
3 | export function shuffle(list: T[]) {
4 | return sampleSize(list, list.length)
5 | }
6 |
--------------------------------------------------------------------------------
/src/snakeCase.ts:
--------------------------------------------------------------------------------
1 | import { words } from './words'
2 |
3 | export function snakeCase(str: string) {
4 | return words(str).join('_')
5 | }
6 |
--------------------------------------------------------------------------------
/src/sum.ts:
--------------------------------------------------------------------------------
1 | export function sum(list?: number[]) {
2 | return (list || []).reduce((x, y) => x + y, 0)
3 | }
4 |
--------------------------------------------------------------------------------
/src/template.ts:
--------------------------------------------------------------------------------
1 | export const template = (
2 | str: string,
3 | data: Record,
4 | regex = /\{\{(.+?)\}\}/g
5 | ) => {
6 | return Array.from(str.matchAll(regex)).reduce((acc, match) => {
7 | return acc.replace(match[0], data[match[1].trim()])
8 | }, str)
9 | }
10 |
--------------------------------------------------------------------------------
/src/throttle.ts:
--------------------------------------------------------------------------------
1 | export function throttle(
2 | f: (...args: T) => any,
3 | wait: number
4 | ) {
5 | let timer: number | null
6 | return (...args: T) => {
7 | if (timer) {
8 | return
9 | }
10 | timer = setTimeout(() => {
11 | f(...args)
12 | timer = null
13 | }, wait) as any as number
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/uniq.ts:
--------------------------------------------------------------------------------
1 | export function uniq(list: T[]): T[] {
2 | return [...new Set(list)]
3 | }
4 |
--------------------------------------------------------------------------------
/src/uniqBy.ts:
--------------------------------------------------------------------------------
1 | export function uniqBy(list: T[], by: (value: T) => unknown): T[] {
2 | const uniqMap: { [key: string]: T } = {}
3 | for (let item of list ?? []) {
4 | const key = by(item) as unknown as string
5 | if (!uniqMap[key]) {
6 | uniqMap[key] = item
7 | }
8 | }
9 | return Object.values(uniqMap)
10 | }
11 |
--------------------------------------------------------------------------------
/src/unzip.ts:
--------------------------------------------------------------------------------
1 | export function unzip(data: any[][]): any[][] {
2 | const maxLength = Math.max(...data.map(row => row.length))
3 | return Array.from({ length: maxLength }, (_, i) => data.map(row => row[i]))
4 | }
5 |
--------------------------------------------------------------------------------
/src/words.ts:
--------------------------------------------------------------------------------
1 | export function words(str: string) {
2 | return str
3 | .replace(/[A-Z]+/g, x => `.${x.toLowerCase()}`)
4 | .replace(/[^\w]+/g, '.')
5 | .split('.')
6 | .filter(Boolean)
7 | }
8 |
--------------------------------------------------------------------------------
/src/zip.ts:
--------------------------------------------------------------------------------
1 | export function zip(...arrays: any[][]): any[][] {
2 | const maxLength = Math.max(...arrays.map(arr => arr.length))
3 | return Array.from({ length: maxLength }, (_, i) => arrays.map(arr => arr[i]))
4 | }
5 |
--------------------------------------------------------------------------------
/test/assign.spec.ts:
--------------------------------------------------------------------------------
1 | import { assign } from '../src'
2 |
3 | describe('assign', () => {
4 | it('handles both null input', () => {
5 | const result = assign(null, null)
6 | expect(result).toEqual({})
7 | })
8 |
9 | it('handles null first input', () => {
10 | const result = assign({ a: '1' }, null)
11 | expect(result).toEqual({ a: '1' })
12 | })
13 |
14 | it('handles null last input', () => {
15 | const result = assign(null, { a: '1' })
16 | expect(result).toEqual({ a: '1' })
17 | })
18 |
19 | it('correctly assign a with values from b', () => {
20 | const target = { a: 2, b: 2 }
21 | const result = assign(target, {})
22 | expect(result).toEqual(target)
23 | })
24 |
25 | it('handles target have unique value', () => {
26 | const source = { a: 3, b: 5 }
27 | const result = assign({}, source)
28 | expect(result).toEqual(source)
29 | })
30 |
31 | it('handles source have unique value', () => {
32 | const target = { a: 2, b: 2 }
33 | const source = { a: 3, b: 5 }
34 | const result = assign(target, source)
35 | expect(result).toEqual(source)
36 | })
37 |
38 | it('should merge nested properties correctly', () => {
39 | const target = { a: { b: 2, c: 3 } }
40 | const source1 = { a: { c: 4, d: 5 } }
41 | const source2 = { a: { d: 6, e: 7 } }
42 | const originTarget = { ...target }
43 | const result = assign(target, source1, source2)
44 |
45 | expect(result).toEqual({ a: { d: 6, e: 7 } })
46 | expect(result).not.toBe(originTarget)
47 | })
48 | })
49 |
--------------------------------------------------------------------------------
/test/async.spec.ts:
--------------------------------------------------------------------------------
1 | import { retry, AbortError, filter, map, sleep } from '../src'
2 | import timeSpan from 'time-span'
3 |
4 | describe('async filter', function () {
5 | it('expect work', async () => {
6 | const r1 = await filter([Promise.resolve(1), 2, 3, 4], (x: any) =>
7 | Boolean(x % 2)
8 | )
9 | const r2 = await filter([Promise.resolve(1), 2, 3, 4], (x: any) =>
10 | Promise.resolve(Boolean(x % 2))
11 | )
12 |
13 | expect(r1).toStrictEqual([1, 3])
14 | expect(r2).toStrictEqual([1, 3])
15 | })
16 | })
17 |
18 | describe('async map', function () {
19 | const input = [
20 | Promise.resolve(1),
21 | Promise.resolve(2),
22 | Promise.resolve(3),
23 | 4,
24 | 5,
25 | 6,
26 | ]
27 |
28 | const addOne = async (n: number | Promise) => {
29 | n = await n
30 | return n + 1
31 | }
32 |
33 | it('expect work', async () => {
34 | const r = await map(input, x => addOne(x))
35 | expect(r).toStrictEqual([2, 3, 4, 5, 6, 7])
36 | })
37 |
38 | it('expect work with concurrency', async () => {
39 | const ts = timeSpan()
40 | const r = await map(
41 | input,
42 | async x => {
43 | await sleep(300)
44 | return addOne(x)
45 | },
46 | { concurrency: 1 }
47 | )
48 | const time = ts()
49 | expect(r).toStrictEqual([2, 3, 4, 5, 6, 7])
50 | expect(time).toBeGreaterThan(1800)
51 | expect(time).toBeLessThan(2000)
52 | })
53 |
54 | it('expect work with concurrency 2', async () => {
55 | const ts = timeSpan()
56 | const r = await map(
57 | input,
58 | async x => {
59 | await sleep(300)
60 | return addOne(x)
61 | },
62 | { concurrency: 2 }
63 | )
64 | const time = ts()
65 | expect(r).toStrictEqual([2, 3, 4, 5, 6, 7])
66 | expect(time).toBeGreaterThan(900)
67 | expect(time).toBeLessThan(1000)
68 | })
69 |
70 | it('expect work with concurrency 3', async () => {
71 | const ts = timeSpan()
72 | const r = await map(
73 | input,
74 | async (x, i) => {
75 | await sleep(i * 100)
76 | return addOne(x)
77 | },
78 | { concurrency: 2 }
79 | )
80 | const time = ts()
81 | expect(r).toStrictEqual([2, 3, 4, 5, 6, 7])
82 | expect(time).toBeGreaterThan(900)
83 | expect(time).toBeLessThan(1000)
84 | })
85 | })
86 |
87 | describe('async retry', function () {
88 | it('expect work', async () => {
89 | let i = 0
90 | const result = 100
91 |
92 | const data = await retry(
93 | async attemptNumber => {
94 | i++
95 | return attemptNumber === 3 ? result : Promise.reject(new Error('error'))
96 | },
97 | {
98 | times: 3,
99 | }
100 | )
101 |
102 | expect(data).toStrictEqual(100)
103 | expect(i).toStrictEqual(3)
104 | })
105 |
106 | it('abort', async () => {
107 | let i = 0
108 | const err = new AbortError('hello')
109 |
110 | try {
111 | await retry(
112 | async () => {
113 | i++
114 | return Promise.reject(err)
115 | },
116 | {
117 | times: 3,
118 | }
119 | )
120 | } catch (e) {
121 | expect(e).toStrictEqual(err)
122 | }
123 |
124 | expect(i).toStrictEqual(1)
125 | })
126 |
127 | it('onFailedAttempt can return a promise to add a delay', async () => {
128 | const waitFor = 1000
129 | const start = Date.now()
130 | const result = 100
131 | let isCalled: boolean
132 |
133 | const r = await retry(
134 | async () => {
135 | if (isCalled) {
136 | return result
137 | }
138 |
139 | isCalled = true
140 |
141 | throw new Error('error')
142 | },
143 | {
144 | onFailedAttempt: async () => {
145 | await sleep(waitFor)
146 | },
147 | }
148 | )
149 |
150 | expect(Date.now()).toBeGreaterThanOrEqual(start + waitFor)
151 | expect(r).toEqual(result)
152 | })
153 | })
154 |
--------------------------------------------------------------------------------
/test/camelCase.spec.ts:
--------------------------------------------------------------------------------
1 | import { camelCase } from '../src'
2 |
3 | describe('camelCase', function () {
4 | it('should work', function () {
5 | expect(camelCase('foo')).toEqual('foo')
6 | expect(camelCase('foo-bar')).toEqual('fooBar')
7 | expect(camelCase('foo--bar')).toEqual('fooBar')
8 | expect(camelCase('--foo--bar')).toEqual('fooBar')
9 | expect(camelCase('FOO-BAR')).toEqual('fooBar')
10 | expect(camelCase('-foo-bar-')).toEqual('fooBar')
11 | expect(camelCase('--foo--bar--')).toEqual('fooBar')
12 | expect(camelCase('foo.bar')).toEqual('fooBar')
13 | expect(camelCase('foo..bar')).toEqual('fooBar')
14 | expect(camelCase('..foo..bar..')).toEqual('fooBar')
15 | expect(camelCase(' foo bar ')).toEqual('fooBar')
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/test/castArray.spec.ts:
--------------------------------------------------------------------------------
1 | import { castArray } from '../src'
2 |
3 | describe('castArray function', () => {
4 | it('should convert a non-array value to an array', () => {
5 | expect(castArray(42)).toEqual([42])
6 | expect(castArray('hello')).toEqual(['hello'])
7 | expect(castArray(null)).toEqual([null])
8 | })
9 |
10 | it('should return the input array unchanged', () => {
11 | const inputArray = [1, 2, 3]
12 | expect(castArray(inputArray)).toBe(inputArray)
13 | })
14 |
15 | it('should return an empty array if no arguments are provided', () => {
16 | expect(castArray()).toEqual([])
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/test/chunk.spec.ts:
--------------------------------------------------------------------------------
1 | import { chunk } from '../src'
2 |
3 | describe('chunk', function () {
4 | const array = [0, 1, 2, 3, 4, 5]
5 |
6 | it('should work', function () {
7 | const actual = chunk(array, 3)
8 | expect(actual).toEqual([
9 | [0, 1, 2],
10 | [3, 4, 5],
11 | ])
12 | })
13 |
14 | it('should work with string', function () {
15 | const actual = chunk('abcdefghi', 3)
16 | expect(actual).toEqual([
17 | ['a', 'b', 'c'],
18 | ['d', 'e', 'f'],
19 | ['g', 'h', 'i'],
20 | ])
21 | })
22 |
23 | it('should work with no size argument', function () {
24 | const actual = chunk(array)
25 | expect(actual).toEqual([[0], [1], [2], [3], [4], [5]])
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/test/clone.spec.ts:
--------------------------------------------------------------------------------
1 | import { clone, cloneDeep } from '../src'
2 |
3 | describe('clone', function () {
4 | it('should work', function () {
5 | expect(clone({ a: 3, b: 4 })).toEqual({ a: 3, b: 4 })
6 | expect(clone([3, 4, 5])).toEqual([3, 4, 5])
7 | expect(clone(3)).toEqual(3)
8 | })
9 | it('should work with shallow clone', function () {
10 | const o = { a: { b: 4 } }
11 | expect(clone(o)).toEqual(o)
12 | expect(clone(o).a).toBe(o.a)
13 | expect(clone(o)).not.toBe(o)
14 | })
15 | it('should work with date', function () {
16 | const d = new Date()
17 | expect(clone(d)).toBe(d)
18 | // expect(clone(d)).not.toBe(d)
19 | })
20 | })
21 |
22 | describe('cloneDeep', function () {
23 | it('should work', function () {
24 | const o = { a: { b: 4 } }
25 | expect(cloneDeep(o)).toEqual(o)
26 | expect(cloneDeep(o).a === o.a).toBe(false)
27 | expect(cloneDeep(o).a).not.toBe(o.a)
28 | expect(cloneDeep(o)).not.toBe(o)
29 | })
30 | })
31 |
--------------------------------------------------------------------------------
/test/compact.spec.ts:
--------------------------------------------------------------------------------
1 | import { compact } from '../src'
2 |
3 | describe('compact', function () {
4 | it('should work', function () {
5 | expect(compact([1, 0, 2, false])).toEqual([1, 2])
6 |
7 | expect(compact([{}, undefined, null, 3])).toEqual([{}, 3])
8 |
9 | expect(compact(['a', 'b', 'c', '', 'd'])).toEqual(['a', 'b', 'c', 'd'])
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/test/compose.spec.ts:
--------------------------------------------------------------------------------
1 | import { compose, flowRight } from '../src'
2 |
3 | describe('compose', () => {
4 | it('should work', () => {
5 | const double = (x: number) => x * 2
6 | const addFive = (x: number) => x + 5
7 |
8 | expect(compose(double)(5)).toBe(10)
9 | expect(compose(addFive, double)(5)).toBe(15)
10 | expect(compose(addFive, double, addFive, double)(5)).toBe(35)
11 |
12 | expect(compose).toBe(flowRight)
13 | })
14 |
15 | it('shoul work with multiple arguments', () => {
16 | const square = (x: number) => x * x
17 | const add = (x: number, y: number) => x + y
18 |
19 | expect(compose(square, add)(1, 2)).toBe(9)
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/test/debounce.spec.ts:
--------------------------------------------------------------------------------
1 | import { debounce, sleep } from '../src'
2 |
3 | describe('debounce', function () {
4 | it('only executes once when called rapidly', async function () {
5 | let num = 0
6 | let func = debounce(() => num++, 500)
7 |
8 | func()
9 | func()
10 | func()
11 | expect(num).toEqual(0)
12 | await sleep(610).then(() => expect(num).toEqual(1))
13 | })
14 | })
15 |
--------------------------------------------------------------------------------
/test/defaults.spec.ts:
--------------------------------------------------------------------------------
1 | import { defaults } from '../src'
2 |
3 | describe('get', function () {
4 | it('should work', function () {
5 | expect(defaults({ a: 1 }, { b: 2 }, { a: 3 })).toEqual({ a: 1, b: 2 })
6 | expect(defaults({ a: 1, b: 2 }, { b: 3 }, { c: 3 })).toEqual({
7 | a: 1,
8 | b: 2,
9 | c: 3,
10 | })
11 | expect(defaults({ a: null }, { a: 1 })).toEqual({ a: null })
12 | expect(defaults({ a: undefined }, { a: 1 })).toEqual({ a: 1 })
13 | })
14 | })
15 |
--------------------------------------------------------------------------------
/test/difference.spec.ts:
--------------------------------------------------------------------------------
1 | import { difference, differenceBy } from '../src'
2 |
3 | describe('max', function () {
4 | it('should work', function () {
5 | expect(difference([1, 2, 3, 4])).toEqual([1, 2, 3, 4])
6 | expect(difference([1, 2, 3, 4], [])).toEqual([1, 2, 3, 4])
7 | expect(difference([1, 2, 3, 4], [1, 3, 5])).toEqual([2, 4])
8 | })
9 |
10 | it('should work with differenceBy', function () {
11 | expect(difference).toBe(differenceBy)
12 | expect(differenceBy([{ a: 3 }, { a: 4 }], [], x => x.a)).toEqual([
13 | { a: 3 },
14 | { a: 4 },
15 | ])
16 | expect(differenceBy([{ a: 3 }, { a: 4 }], [{ a: 3 }], x => x.a)).toEqual([
17 | { a: 4 },
18 | ])
19 | expect(differenceBy([3.5, 4.5], [3.8, 5.8], Math.floor)).toEqual([4.5])
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/test/get.spec.ts:
--------------------------------------------------------------------------------
1 | import { get } from '../src'
2 |
3 | describe('get', function () {
4 | const object = { a: 1, b: 2, c: 3, d: 4 }
5 |
6 | it('should work', function () {
7 | const a = get(object, ['a'])
8 | const b = get(object, 'b')
9 |
10 | expect(a).toEqual(1)
11 | expect(b).toEqual(2)
12 | })
13 |
14 | it('should work when source is null or undefined', function () {
15 | const a = get(null, 'a')
16 | const b = get(undefined, 'a')
17 |
18 | expect(a).toEqual(undefined)
19 | expect(b).toEqual(undefined)
20 | })
21 |
22 | it('should work with default value', function () {
23 | const actual = get(object, 'a.b', 404)
24 |
25 | expect(actual).toEqual(404)
26 | })
27 |
28 | it('should work with array', function () {
29 | const actual = get({ a: { b: 3 } }, ['a', 'b'])
30 |
31 | expect(actual).toEqual(3)
32 | })
33 |
34 | it('should work with null', function () {
35 | const actual = get({ a: null }, 'a', 404)
36 |
37 | expect(actual).toEqual(null)
38 | })
39 | })
40 |
--------------------------------------------------------------------------------
/test/groupBy.spec.ts:
--------------------------------------------------------------------------------
1 | import { groupBy } from '../src'
2 |
3 | describe('groupBy', function () {
4 | it('should work', function () {
5 | const actual = groupBy(['one', 'two', 'three'], x => x.length)
6 | expect(actual).toEqual({ '3': ['one', 'two'], '5': ['three'] })
7 | })
8 |
9 | it('should work with function', function () {
10 | const actual = groupBy([1.2, 3.4, 3.2], Math.floor)
11 | expect(actual).toEqual({ '1': [1.2], '3': [3.4, 3.2] })
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/test/head.spec.ts:
--------------------------------------------------------------------------------
1 | import { head, tail, take } from '../src'
2 |
3 | describe('groupBy', function () {
4 | const list = [1, 2, 3, 4, 5]
5 | it('head should work', function () {
6 | expect(head()).toEqual(undefined)
7 | expect(head([])).toEqual(undefined)
8 | expect(head(list)).toEqual(1)
9 | })
10 |
11 | it('tail should work', function () {
12 | expect(tail()).toEqual([])
13 | expect(tail([])).toEqual([])
14 | expect(tail(list)).toEqual([2, 3, 4, 5])
15 | })
16 |
17 | it('take should work', function () {
18 | expect(take()).toEqual([])
19 | expect(take([])).toEqual([])
20 | expect(take([1, 2, 3])).toEqual([1])
21 | expect(take([1, 2, 3], 2)).toEqual([1, 2])
22 | expect(take([1, 2, 3], 5)).toEqual([1, 2, 3])
23 | expect(take([1, 2, 3], 0)).toEqual([])
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/test/intersection.spec.ts:
--------------------------------------------------------------------------------
1 | import { intersection } from '../src'
2 | import { intersectionBy } from '../src'
3 |
4 | describe('should work', function () {
5 | let list = [['a', 'b', 'c'], ['a', 'c'], ['a'], ['a', 'd']]
6 | it('should work', function () {
7 | expect(intersection()).toEqual([])
8 | expect(intersection([])).toEqual([])
9 | expect(intersection([2, 1], [4, 2], [1, 2])).toEqual([2])
10 | expect(intersection([2, 1])).toEqual([2, 1])
11 | expect(intersection(...list)).toEqual(['a'])
12 | })
13 |
14 | it('should work with by', function () {
15 | expect(intersectionBy()).toEqual([])
16 | expect(intersectionBy([])).toEqual([])
17 | expect(intersectionBy([{ x: 1 }])).toEqual([{ x: 1 }])
18 | expect(intersectionBy([2, 1], [4, 2], [1, 2], Math.floor)).toEqual([2])
19 | expect(intersectionBy([{ x: 1 }], [{ x: 2 }, { x: 1 }], x => x.x)).toEqual([
20 | { x: 1 },
21 | ])
22 | })
23 | })
24 |
--------------------------------------------------------------------------------
/test/isArray.spec.ts:
--------------------------------------------------------------------------------
1 | import { isArray } from '../src'
2 |
3 | describe('isObject', function () {
4 | it('should work', function () {
5 | expect(isArray([])).toBe(true)
6 | expect(isArray({ length: 3 })).toBe(false)
7 | })
8 | })
9 |
--------------------------------------------------------------------------------
/test/isBoolean.ts:
--------------------------------------------------------------------------------
1 | import { isBoolean } from '../src'
2 |
3 | describe('isBoolean', function () {
4 | it('should work', function () {
5 | expect(isBoolean(true)).toBe(true)
6 | expect(isBoolean(false)).toBe(false)
7 | })
8 | })
9 |
--------------------------------------------------------------------------------
/test/isEqual.spec.ts:
--------------------------------------------------------------------------------
1 | import { isEqual } from '../src'
2 |
3 | describe('isEqual', function () {
4 | it('should work', function () {
5 | expect(isEqual({ a: 3 }, { a: 3 })).toEqual(true)
6 | expect(isEqual([{ a: 3 }], [{ a: 3 }])).toEqual(true)
7 | expect(isEqual(new Map(), new Map())).toEqual(true)
8 | expect(isEqual(new Map([['a', 3]]), new Map([['a', 3]]))).toEqual(true)
9 | expect(isEqual(new Set(['a', 'b', 'c']), new Set(['a', 'b', 'c']))).toEqual(
10 | true
11 | )
12 | })
13 |
14 | it('should work with primitive value', function () {
15 | expect(isEqual(3, 3)).toEqual(true)
16 | expect(isEqual(NaN, NaN)).toEqual(true)
17 | expect(isEqual(3n, 3n)).toEqual(true)
18 | })
19 | })
20 |
--------------------------------------------------------------------------------
/test/isObject.spec.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from '../src'
2 |
3 | describe('isObject', function () {
4 | it('should work', function () {
5 | expect(isObject({})).toBe(true)
6 | expect(isObject([])).toBe(true)
7 | expect(isObject(() => {})).toBe(true)
8 | expect(isObject(new Date())).toBe(true)
9 | expect(isObject(3)).toBe(false)
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/test/isPlainObject.spec.ts:
--------------------------------------------------------------------------------
1 | import { isPlainObject } from '../src'
2 |
3 | describe('isObject', function () {
4 | it('should work', function () {
5 | expect(isPlainObject({})).toBe(true)
6 | expect(isPlainObject(Object.create(null))).toBe(true)
7 |
8 | expect(isPlainObject([])).toBe(false)
9 | expect(isPlainObject(() => {})).toBe(false)
10 | expect(isPlainObject(new Date())).toBe(false)
11 | expect(isPlainObject(3)).toBe(false)
12 | expect(isPlainObject(Object.create({ a: 3 }))).toBe(false)
13 | })
14 | })
15 |
--------------------------------------------------------------------------------
/test/isPromise.spec.ts:
--------------------------------------------------------------------------------
1 | import { isPromise } from '../src'
2 |
3 | describe('isPromise', function () {
4 | it('should work', function () {
5 | async function f() {}
6 |
7 | expect(isPromise(Promise.resolve())).toBe(true)
8 | expect(isPromise(f())).toBe(true)
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/test/isTypedArray.spec.ts:
--------------------------------------------------------------------------------
1 | import { isTypedArray } from '../src'
2 |
3 | describe('isTypedArray', function () {
4 | it('should work', function () {
5 | expect(isTypedArray(new Uint16Array([3, 4, 5]))).toBe(true)
6 | expect(isTypedArray([])).toBe(false)
7 | })
8 | })
9 |
--------------------------------------------------------------------------------
/test/kebabCase.spec.ts:
--------------------------------------------------------------------------------
1 | import { kebabCase } from '../src'
2 |
3 | describe('kebabCase', function () {
4 | it('should work', function () {
5 | expect(kebabCase('foo')).toEqual('foo')
6 | expect(kebabCase('foo-bar')).toEqual('foo-bar')
7 | expect(kebabCase('foo--bar')).toEqual('foo-bar')
8 | expect(kebabCase('--foo--bar')).toEqual('foo-bar')
9 | expect(kebabCase('FOO-BAR')).toEqual('foo-bar')
10 | expect(kebabCase('-foo-bar-')).toEqual('foo-bar')
11 | expect(kebabCase('--foo--bar--')).toEqual('foo-bar')
12 | expect(kebabCase('foo.bar')).toEqual('foo-bar')
13 | expect(kebabCase('foo..bar')).toEqual('foo-bar')
14 | expect(kebabCase('..foo..bar..')).toEqual('foo-bar')
15 | expect(kebabCase(' foo bar ')).toEqual('foo-bar')
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/test/keyBy.spec.ts:
--------------------------------------------------------------------------------
1 | import { keyBy } from '../src'
2 |
3 | describe('groupBy', function () {
4 | const list = [
5 | { id: 1, name: 'shuifeng' },
6 | { id: 2, name: 'shanyue' },
7 | ]
8 |
9 | it('should work', function () {
10 | const actual = keyBy(list, x => x.id)
11 | expect(actual).toEqual({
12 | 1: {
13 | id: 1,
14 | name: 'shuifeng',
15 | },
16 | 2: {
17 | id: 2,
18 | name: 'shanyue',
19 | },
20 | })
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/test/mapKeys.spec.ts:
--------------------------------------------------------------------------------
1 | import { mapKeys, mapValues } from '../src'
2 |
3 | describe('mapKeys', function () {
4 | it('should work', () => {
5 | expect(mapKeys({ a: 1, b: 2 }, String)).toStrictEqual({
6 | 1: 1,
7 | 2: 2,
8 | })
9 |
10 | expect(mapKeys({ a: 3 }, () => 'b')).toStrictEqual({
11 | b: 3,
12 | })
13 |
14 | expect(mapKeys({ a: 3, b: 4 }, String)).toStrictEqual({
15 | 3: 3,
16 | 4: 4,
17 | })
18 | })
19 |
20 | it('should work with type', () => {
21 | type Input = {
22 | a: number
23 | b: number
24 | c?: number
25 | }
26 | const input: Input = {
27 | a: 3,
28 | b: 4,
29 | }
30 | // expect type same with Input
31 | const actual = mapKeys(input, String)
32 | expect(actual).toStrictEqual({
33 | 3: 3,
34 | 4: 4,
35 | })
36 | })
37 | })
38 |
39 | describe('mapValues', function () {
40 | it('should work', () => {
41 | expect(mapValues({ a: 3, b: 4 }, x => x + 1)).toStrictEqual({
42 | a: 4,
43 | b: 5,
44 | })
45 |
46 | expect(mapValues({ a: 1, b: 2 }, String)).toStrictEqual({
47 | a: '1',
48 | b: '2',
49 | })
50 |
51 | expect(mapValues({ a: 3 }, () => 'b')).toStrictEqual({
52 | a: 'b',
53 | })
54 |
55 | expect(mapValues({ a: 3, b: 4 })).toStrictEqual({
56 | a: 3,
57 | b: 4,
58 | })
59 | })
60 | })
61 |
--------------------------------------------------------------------------------
/test/max.spec.ts:
--------------------------------------------------------------------------------
1 | import { max, maxBy } from '../src'
2 |
3 | describe('max', function () {
4 | it('should work', function () {
5 | expect(max([-1, 0, 1])).toEqual(1)
6 | expect(max([1])).toEqual(1)
7 | expect(max([])).toEqual(undefined)
8 | expect(max()).toEqual(undefined)
9 | })
10 | })
11 |
12 | describe('maxBy', function () {
13 | it('should work', function () {
14 | expect(
15 | maxBy(
16 | [
17 | {
18 | a: 3,
19 | },
20 | {
21 | a: 4,
22 | },
23 | ],
24 | x => x.a
25 | )
26 | ).toEqual({ a: 4 })
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/test/memoize.spec.ts:
--------------------------------------------------------------------------------
1 | import { memoize } from '../src'
2 |
3 | describe('memorize', function () {
4 | it('memorize function should return cached result when input is same', () => {
5 | let count = 0
6 | const fn = (x: number): number => {
7 | count++
8 | return x * x
9 | }
10 | const memoizedFunction = memoize(fn)
11 |
12 | const firstResult = memoizedFunction(5) // 输出 25,这次需要计算
13 | const secondResult = memoizedFunction(5) // 输出 25,这次直接从缓存中获取结果,不需要计算
14 |
15 | // 验证memoizedFunction(5)的结果是正确的
16 | expect(firstResult).toEqual(25)
17 | expect(secondResult).toEqual(25)
18 |
19 | // 验证fn只被调用了一次
20 | expect(count).toEqual(1)
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/test/merge.spec.ts:
--------------------------------------------------------------------------------
1 | import { merge } from '../src'
2 |
3 | describe('merge', function () {
4 | it('should work', function () {
5 | expect(
6 | merge(
7 | {
8 | a: 3,
9 | },
10 | {
11 | b: 4,
12 | }
13 | )
14 | ).toEqual({
15 | a: 3,
16 | b: 4,
17 | })
18 | })
19 |
20 | it('should work with array', function () {
21 | expect(merge(['a', 'b'], ['c'])).toEqual(['c', 'b'])
22 | })
23 |
24 | // lodash.js Test Case
25 | // https://github.com/lodash/lodash/blob/master/test/merge.test.js#L8
26 | it('should merge `source` into `object`', function () {
27 | const names = {
28 | characters: [{ name: 'barney' }, { name: 'fred' }],
29 | }
30 |
31 | const ages = {
32 | characters: [{ age: 36 }, { age: 40 }],
33 | }
34 |
35 | const heights = {
36 | characters: [{ height: '5\'4"' }, { height: '5\'5"' }],
37 | }
38 |
39 | const expected = {
40 | characters: [
41 | { name: 'barney', age: 36, height: '5\'4"' },
42 | { name: 'fred', age: 40, height: '5\'5"' },
43 | ],
44 | }
45 |
46 | expect(merge(names, ages, heights)).toEqual(expected)
47 | })
48 |
49 | it('should work with more arguments', function () {
50 | const actual = merge({ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 })
51 |
52 | expect(actual).toEqual({ a: 4 })
53 | })
54 |
55 | it('should work with function', function () {
56 | const fn = function () {}
57 |
58 | const actual = merge({ a: fn }, { a: fn }, { a: {} })
59 |
60 | expect(actual).toEqual({ a: {} })
61 | })
62 |
63 | it('should work with deepClone', function () {
64 | const o = {
65 | a: { b: 3 },
66 | }
67 |
68 | expect(merge({}, o).a).toEqual(o.a)
69 | expect(merge({}, o).a).not.toBe(o.a)
70 | })
71 | })
72 |
--------------------------------------------------------------------------------
/test/min.spec.ts:
--------------------------------------------------------------------------------
1 | import { min, minBy } from '../src'
2 |
3 | describe('min', function () {
4 | it('should work', function () {
5 | expect(min([-1, 0, 1])).toEqual(-1)
6 | expect(min([1])).toEqual(1)
7 | expect(min([])).toEqual(undefined)
8 | expect(min()).toEqual(undefined)
9 | })
10 | })
11 |
12 | describe('minBy', function () {
13 | it('should work', function () {
14 | expect(
15 | minBy(
16 | [
17 | {
18 | a: 3,
19 | },
20 | {
21 | a: 4,
22 | },
23 | ],
24 | x => x.a
25 | )
26 | ).toEqual({ a: 3 })
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/test/nth.spec.ts:
--------------------------------------------------------------------------------
1 | import { nth } from '../src'
2 |
3 | describe('min', function () {
4 | const list = [1, 2, 3, 4, 5, 6]
5 | it('should work', function () {
6 | expect(nth(list, 3)).toEqual(4)
7 | expect(nth(list, -3)).toEqual(4)
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/test/omit.spec.ts:
--------------------------------------------------------------------------------
1 | import { omit } from '../src'
2 |
3 | describe('omit', function () {
4 | const object = { a: 1, b: 2, c: 3, d: 4 }
5 |
6 | it('should work', function () {
7 | const actual = omit(object, ['a', 'c', 'z'])
8 |
9 | expect(actual).toEqual({ b: 2, d: 4 })
10 |
11 | expect(omit(object)).toEqual(object)
12 |
13 | expect(omit(undefined)).toEqual({})
14 | })
15 |
16 | it('should work with second arguments', function () {
17 | const actual = omit(object)
18 |
19 | expect(actual).toEqual(object)
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/test/omitBy.spec.ts:
--------------------------------------------------------------------------------
1 | import { omitBy } from '../src'
2 |
3 | describe('omitBy', function () {
4 | const object = { a: 1, b: 2, c: 3, d: 4 }
5 |
6 | it('should work', function () {
7 | expect(omitBy(object, x => x === 3)).toEqual({ a: 1, b: 2, d: 4 })
8 | expect(omitBy(object, undefined as unknown as () => boolean)).toEqual({})
9 | })
10 |
11 | it('should work with value', function () {
12 | const actual = omitBy(object, n => {
13 | return n === 1 || n === 3
14 | })
15 |
16 | expect(actual).toEqual({ b: 2, d: 4 })
17 | })
18 |
19 | it('should work with key', function () {
20 | const actual = omitBy(object, (_, n) => {
21 | return n === 'a' || n === 'b'
22 | })
23 |
24 | expect(actual).toEqual({ c: 3, d: 4 })
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/test/once.spec.ts:
--------------------------------------------------------------------------------
1 | import { once } from '../src'
2 |
3 | describe('once', function () {
4 | it('once function should only allow the inner function to be called once', () => {
5 | let count = 0
6 | const fn = () => count++
7 | const onceFn = once(fn)
8 |
9 | onceFn()
10 | onceFn()
11 |
12 | expect(count).toEqual(1)
13 | })
14 | })
15 |
--------------------------------------------------------------------------------
/test/pick.spec.ts:
--------------------------------------------------------------------------------
1 | import { pick } from '../src'
2 |
3 | describe('pick', function () {
4 | const object = { a: 1, b: 2, c: 3, d: 4 }
5 |
6 | it('should work', function () {
7 | const actual = pick(object, ['a', 'c', 'z'])
8 |
9 | expect(actual).toEqual({ a: 1, c: 3 })
10 |
11 | expect(pick({ a: undefined }, ['a'])).toStrictEqual({ a: undefined })
12 | expect(pick({}, ['a'])).toStrictEqual({})
13 | expect(pick(object)).toEqual({})
14 | expect(pick(undefined)).toEqual({})
15 | })
16 |
17 | it('should work with second arguments', function () {
18 | const actual = pick(object)
19 |
20 | expect(actual).toEqual({})
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/test/pickBy.spec.ts:
--------------------------------------------------------------------------------
1 | import { pickBy } from '../src'
2 |
3 | describe('pickBy', function () {
4 | const object = { a: 1, b: 2, c: 3, d: 4 }
5 |
6 | it('should work', function () {
7 | expect(pickBy(object, x => x === 3)).toEqual({ c: 3 })
8 | expect(pickBy(object, undefined as unknown as () => boolean)).toEqual(
9 | object
10 | )
11 |
12 | expect(pickBy({ a: undefined })).toEqual({})
13 | })
14 |
15 | it('should work with value', function () {
16 | const actual = pickBy(object, function (n) {
17 | return n === 1 || n === 3
18 | })
19 |
20 | expect(actual).toEqual({ a: 1, c: 3 })
21 | })
22 |
23 | it('should work with key', function () {
24 | const actual = pickBy(object, function (_, n) {
25 | return n === 'a' || n === 'b'
26 | })
27 |
28 | expect(actual).toEqual({ a: 1, b: 2 })
29 | })
30 | })
31 |
--------------------------------------------------------------------------------
/test/property.spec.ts:
--------------------------------------------------------------------------------
1 | import { property } from '../src'
2 |
3 | interface Person {
4 | name: string
5 | age: number
6 | address: {
7 | city: string
8 | country: string
9 | }
10 | }
11 |
12 | describe('property function', () => {
13 | const objects = [{ a: { b: 2 } }, { a: { b: 1 } }]
14 |
15 | it('should return the correct property values', () => {
16 | const getB = property('a.b')
17 | expect(getB(objects[0])).toBe(2)
18 | expect(getB(objects[1])).toBe(1)
19 | })
20 |
21 | it('should handle non-existing properties gracefully', () => {
22 | const getC = property('a.c')
23 | expect(getC(objects[0])).toBeUndefined()
24 | })
25 |
26 | const person: Person = {
27 | name: 'John Doe',
28 | age: 30,
29 | address: {
30 | city: 'New York',
31 | country: 'USA',
32 | },
33 | }
34 |
35 | it('should get the correct property value from an object', () => {
36 | const getName = property('name')
37 | const getCity = property('address.city')
38 |
39 | expect(getName(person)).toBe('John Doe')
40 | expect(getCity(person)).toBe('New York')
41 | })
42 |
43 | it('should handle nested properties', () => {
44 | const getCountry = property('address.country')
45 | expect(getCountry(person)).toBe('USA')
46 | })
47 |
48 | it('should return undefined for non-existing properties', () => {
49 | const getLastName = property('lastName')
50 | const getStreet = property('address.street')
51 |
52 | expect(getLastName(person)).toBeUndefined()
53 | expect(getStreet(person)).toBeUndefined()
54 | })
55 |
56 | it('should handle non-object inputs', () => {
57 | const getLength = property('length')
58 | const getFirstChar = property('0')
59 |
60 | expect(getLength('hello')).toBe(5)
61 | expect(getFirstChar('hello')).toBe('h')
62 | })
63 |
64 | it('should handle array inputs', () => {
65 | const getFirstElement = property('0')
66 | const getNestedElement = property('1.0')
67 | const getArrayElement = property(['1', '0'])
68 | expect(getFirstElement([1, 2, 3])).toBe(1)
69 | expect(
70 | getNestedElement([
71 | [1, 2],
72 | [3, 4],
73 | ])
74 | ).toBe(3)
75 | expect(
76 | getArrayElement([
77 | [1, 2],
78 | [3, 4],
79 | ])
80 | ).toBe(3)
81 | })
82 | })
83 |
--------------------------------------------------------------------------------
/test/random.spec.ts:
--------------------------------------------------------------------------------
1 | import { random } from '../src'
2 |
3 | describe('sampleSize', function () {
4 | it('should work', function () {
5 | expect(random(10, 10)).toEqual(10)
6 | expect(random(0, 0)).toEqual(0)
7 | expect(random(0)).toEqual(0)
8 | expect(random(0, 1)).toBeGreaterThanOrEqual(0)
9 | expect(random(0, 1)).toBeLessThanOrEqual(1)
10 | expect(random()).toBeGreaterThanOrEqual(0)
11 | expect(random()).toBeLessThanOrEqual(1)
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/test/range.spec.ts:
--------------------------------------------------------------------------------
1 | import { range } from '../src'
2 |
3 | describe('range', function () {
4 | it('should work', function () {
5 | expect(range(4)).toEqual([0, 1, 2, 3])
6 | expect(range(-4)).toEqual([0, -1, -2, -3])
7 |
8 | expect(range(1, 5)).toEqual([1, 2, 3, 4])
9 | expect(range(5, 1)).toEqual([5, 4, 3, 2])
10 |
11 | expect(range(0, -4, -1)).toEqual([0, -1, -2, -3])
12 | expect(range(5, 1, -1)).toEqual([5, 4, 3, 2])
13 | expect(range(0, 20, 5)).toEqual([0, 5, 10, 15])
14 |
15 | expect(range(1, 5, 0)).toEqual([1, 1, 1, 1])
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/test/sample.spec.ts:
--------------------------------------------------------------------------------
1 | import { sample } from '../src'
2 |
3 | describe('sample', function () {
4 | const list = [1, 2, 3, 4, 5]
5 |
6 | it('should return a random element', function () {
7 | for (let i = 0; i < 10; i++) {
8 | const actual = sample(list)
9 | expect(list).toContain(actual)
10 | }
11 | })
12 |
13 | it('should return `undefined` when sampling empty collections', function () {
14 | const actual = sample([])
15 | expect(actual).toBe(undefined)
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/test/sampleSize.spec.ts:
--------------------------------------------------------------------------------
1 | import { sampleSize } from '../src'
2 |
3 | describe('sampleSize', function () {
4 | const list = [1, 2, 3, 4, 5]
5 |
6 | it('should return an array of random elements', function () {
7 | const actual = sampleSize(list, 2)
8 |
9 | expect(actual).toHaveLength(2)
10 | expect(actual).toEqual(expect.arrayContaining(actual))
11 | })
12 |
13 | it('should contain elements of the collection', function () {
14 | const actual = sampleSize(list, list.length).sort()
15 |
16 | expect(actual).toStrictEqual(list)
17 | })
18 |
19 | it('should return an empty array when `n` < `1` or `NaN`', function () {
20 | expect(sampleSize(list, 0)).toStrictEqual([])
21 | expect(sampleSize(list, -1)).toStrictEqual([])
22 | expect(sampleSize(list, NaN)).toStrictEqual([])
23 | })
24 |
25 | it('should return all elements when `n` >= `length`', function () {
26 | const actual = sampleSize(list, 10086).sort()
27 |
28 | expect(actual).toStrictEqual(list)
29 | })
30 |
31 | it('should coerce `n` to an integer', function () {
32 | const actual = sampleSize(list, 2.3)
33 |
34 | expect(actual).toHaveLength(2)
35 | })
36 |
37 | it('should return an empty array for empty collections', function () {
38 | const actual = sampleSize([])
39 |
40 | expect(actual).toStrictEqual([])
41 | })
42 | })
43 |
--------------------------------------------------------------------------------
/test/shuffle.spec.ts:
--------------------------------------------------------------------------------
1 | import { shuffle } from '../src'
2 |
3 | describe('sampleSize', function () {
4 | const list = [1, 2, 3, 4, 5]
5 |
6 | it('should work', function () {
7 | const actual = shuffle(list)
8 |
9 | expect(actual).toHaveLength(5)
10 | expect(actual).toEqual(expect.arrayContaining(actual))
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/test/snakeCase.spec.ts:
--------------------------------------------------------------------------------
1 | import { snakeCase } from '../src'
2 |
3 | describe('snakeCase', function () {
4 | it('should work', function () {
5 | expect(snakeCase('foo')).toEqual('foo')
6 | expect(snakeCase('foo-bar')).toEqual('foo_bar')
7 | expect(snakeCase('foo--bar')).toEqual('foo_bar')
8 | expect(snakeCase('--foo--bar')).toEqual('foo_bar')
9 | expect(snakeCase('FOO-BAR')).toEqual('foo_bar')
10 | expect(snakeCase('-foo-bar-')).toEqual('foo_bar')
11 | expect(snakeCase('--foo--bar--')).toEqual('foo_bar')
12 | expect(snakeCase('foo.bar')).toEqual('foo_bar')
13 | expect(snakeCase('foo..bar')).toEqual('foo_bar')
14 | expect(snakeCase('..foo..bar..')).toEqual('foo_bar')
15 | expect(snakeCase(' foo bar ')).toEqual('foo_bar')
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/test/sum.spec.ts:
--------------------------------------------------------------------------------
1 | import { sum } from '../src'
2 |
3 | describe('range', function () {
4 | it('should work', function () {
5 | expect(sum()).toEqual(0)
6 | expect(sum([1])).toEqual(1)
7 | expect(sum([1, 2])).toEqual(3)
8 | expect(sum([1, 2, 3])).toEqual(6)
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/test/template.spec.ts:
--------------------------------------------------------------------------------
1 | import { template } from '../src'
2 |
3 | describe('template function', () => {
4 | it('replaces all occurrences', () => {
5 | const tmp = `
6 | Hello my name is {{name}}. I am a {{type}}.
7 | Not sure why I am {{reason}}.
8 |
9 | Thank You - {{name}}
10 | `
11 | const data = {
12 | name: 'Ray',
13 | type: 'template',
14 | reason: 'so beautiful',
15 | }
16 |
17 | const result = template(tmp, data)
18 | const expected = `
19 | Hello my name is ${data.name}. I am a ${data.type}.
20 | Not sure why I am ${data.reason}.
21 |
22 | Thank You - ${data.name}
23 | `
24 |
25 | expect(result).toEqual(expected)
26 | })
27 |
28 | it('replaces all occurrences given template', () => {
29 | const tmp = `Hello .`
30 | const data = {
31 | name: 'world',
32 | }
33 |
34 | const result = template(tmp, data, /<(.+?)>/g)
35 | expect(result).toEqual(`Hello ${data.name}.`)
36 | })
37 |
38 | it('should replace multiple variables', () => {
39 | expect(
40 | template('{{ greeting }}, {{ name }}!', {
41 | greeting: 'hello',
42 | name: 'world',
43 | })
44 | ).toBe('hello, world!')
45 | })
46 |
47 | it('should ignore whitespace around variable names', () => {
48 | expect(template('hello, {{ name }}!', { name: 'world' })).toBe(
49 | 'hello, world!'
50 | )
51 | })
52 | })
53 |
--------------------------------------------------------------------------------
/test/throttle.spec.ts:
--------------------------------------------------------------------------------
1 | import { throttle, sleep } from '../src'
2 |
3 | describe('throttle', function () {
4 | it('should work', async function () {
5 | let num = 0
6 | let func = throttle(() => num++, 500)
7 |
8 | func()
9 | func()
10 | func()
11 | await sleep(600).then(() => expect(num).toEqual(1))
12 |
13 | func()
14 | await sleep(600).then(() => expect(num).toEqual(2))
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/test/uniq.spec.ts:
--------------------------------------------------------------------------------
1 | import { uniq } from '../src'
2 |
3 | describe('sampleSize', function () {
4 | const list = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6]
5 |
6 | it('should work', function () {
7 | const actual = uniq(list)
8 |
9 | expect(actual).toEqual([1, 2, 3, 4, 5, 6])
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/test/uniqBy.spec.ts:
--------------------------------------------------------------------------------
1 | import { uniqBy } from '../src'
2 |
3 | describe('sampleSize', function () {
4 | const list = [1.1, 2.5, 3.5, 4.1, 5.4, 1.5, 2.3, 3.7, 4.9, 5.3, 6.3]
5 |
6 | const testData = [
7 | { id: 1, name: 'Tom' },
8 | { id: 2, name: 'Jerry' },
9 | { id: 1, name: 'Tom' },
10 | { id: 3, name: 'Spike' },
11 | { id: 2, name: 'Jerry' },
12 | ]
13 |
14 | const testStrings = ['Tom', 'Jerry', 'Tom', 'Spike', 'Jerry']
15 |
16 | it('should work', function () {
17 | const actual = uniqBy(list, Math.floor)
18 |
19 | expect(actual).toEqual([1.1, 2.5, 3.5, 4.1, 5.4, 6.3])
20 |
21 | const actualObj = uniqBy(testData, item => item.id)
22 |
23 | expect(actualObj).toEqual([
24 | { id: 1, name: 'Tom' },
25 | { id: 2, name: 'Jerry' },
26 | { id: 3, name: 'Spike' },
27 | ])
28 |
29 | const actualString = uniqBy(testStrings, item => item)
30 |
31 | expect(actualString).toEqual(['Tom', 'Jerry', 'Spike'])
32 | })
33 | })
34 |
--------------------------------------------------------------------------------
/test/unzip.spec.ts:
--------------------------------------------------------------------------------
1 | import { unzip } from '../src'
2 |
3 | describe('unzip', () => {
4 | it('should correctly unzip an array of arrays', () => {
5 | const input = [
6 | [1, 'a', true],
7 | [2, 'b', false],
8 | [3, 'c', true],
9 | ]
10 | const result = unzip(input)
11 |
12 | expect(result).toEqual([
13 | [1, 2, 3],
14 | ['a', 'b', 'c'],
15 | [true, false, true],
16 | ])
17 | })
18 |
19 | it('should handle empty input', () => {
20 | const input: any[][] = []
21 | const result = unzip(input)
22 |
23 | expect(result).toEqual([])
24 | })
25 |
26 | it('should handle arrays of different lengths', () => {
27 | const input = [
28 | [1, 'a'],
29 | [2, 'b', 3],
30 | [4, 'c'],
31 | ]
32 | const result = unzip(input)
33 |
34 | expect(result).toEqual([
35 | [1, 2, 4],
36 | ['a', 'b', 'c'],
37 | [undefined, 3, undefined],
38 | ])
39 | })
40 | })
41 |
--------------------------------------------------------------------------------
/test/zip.spec.ts:
--------------------------------------------------------------------------------
1 | import { zip } from '../src'
2 |
3 | describe('zip', () => {
4 | it('should zip arrays correctly', () => {
5 | const arr1 = [1, 2, 3]
6 | const arr2 = ['a', 'b', 'c']
7 | const arr3 = [true, false]
8 |
9 | const zipped = zip(arr1, arr2, arr3)
10 |
11 | const expected = [
12 | [1, 'a', true],
13 | [2, 'b', false],
14 | [3, 'c', undefined],
15 | ]
16 |
17 | expect(zipped).toEqual(expected)
18 | })
19 |
20 | it('should handle empty arrays', () => {
21 | const emptyArray: number[] = []
22 | const arr1 = [1, 2, 3]
23 | const zipped = zip(emptyArray, arr1)
24 |
25 | const expected = [
26 | [undefined, 1],
27 | [undefined, 2],
28 | [undefined, 3],
29 | ]
30 |
31 | expect(zipped).toEqual(expected)
32 | })
33 |
34 | it('should handle arrays of different lengths', () => {
35 | const arr1 = [1, 2]
36 | const arr2 = ['a', 'b', 'c']
37 | const arr3: boolean[] = []
38 | const zipped = zip(arr1, arr2, arr3)
39 |
40 | const expected = [
41 | [1, 'a', undefined],
42 | [2, 'b', undefined],
43 | [undefined, 'c', undefined],
44 | ]
45 |
46 | expect(zipped).toEqual(expected)
47 | })
48 | })
49 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs
3 | "include": ["src", "types", "test"],
4 | "compilerOptions": {
5 | "rootDirs": ["src", "test", "types"],
6 | "module": "esnext",
7 | "lib": ["dom", "esnext"],
8 | "target": "ES2020",
9 | "importHelpers": true,
10 | // output .d.ts declaration files for consumers
11 | "declaration": true,
12 | // output .js.map sourcemap files for consumers
13 | "sourceMap": true,
14 | // match output dir to input dir. e.g. dist/index instead of dist/src/index
15 | // stricter type-checking for stronger correctness. Recommended by TS
16 | "strict": true,
17 | // linter checks for common issues
18 | "noImplicitReturns": true,
19 | "noFallthroughCasesInSwitch": true,
20 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative
21 | "noUnusedLocals": true,
22 | "noUnusedParameters": true,
23 | // use Node's module resolution algorithm, instead of the legacy TS one
24 | "moduleResolution": "node",
25 | // transpile JSX to React.createElement
26 | "jsx": "react",
27 | // interop between ESM and CJS modules. Recommended by TS
28 | "esModuleInterop": true,
29 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS
30 | "skipLibCheck": true,
31 | // error out if import and file system have a casing mismatch. Recommended by TS
32 | "forceConsistentCasingInFileNames": true,
33 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc`
34 | "noEmit": true,
35 | "downlevelIteration": true,
36 | "types": ["vitest/globals"],
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup'
2 |
3 | export default defineConfig({
4 | entry: ['src/index.ts'],
5 | splitting: false,
6 | sourcemap: true,
7 | clean: true,
8 | dts: true,
9 | format: ['cjs', 'esm'],
10 | outExtension({ format }) {
11 | return {
12 | js: `.${format}.js`,
13 | }
14 | },
15 | })
16 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitest/config'
2 |
3 | export default defineConfig({
4 | test: {
5 | includeSource: ['test/*'],
6 | globals: true,
7 | coverage: {
8 | reporter: ['text'], // https://vitest.dev/guide/coverage.html#coverage-setup
9 | },
10 | },
11 | })
12 |
--------------------------------------------------------------------------------