@ocavue/utils 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { describe, it, expect } from 'vitest'
4
4
 
5
- import { Counter, DefaultMap, DefaultWeakMap, WeakCounter } from './default-map'
5
+ import { DefaultMap, DefaultWeakMap } from './default-map'
6
6
 
7
7
  describe('DefaultMap', () => {
8
8
  it('creates default values for missing keys', () => {
@@ -281,195 +281,3 @@ describe('DefaultWeakMap', () => {
281
281
  expect(map.has(key)).toBe(true)
282
282
  })
283
283
  })
284
-
285
- describe('Counter', () => {
286
- it('initializes counts to 0', () => {
287
- const counter = new Counter<string>()
288
-
289
- expect(counter.get('key1')).toBe(0)
290
- expect(counter.get('key2')).toBe(0)
291
- })
292
-
293
- it('increments counts', () => {
294
- const counter = new Counter<string>()
295
-
296
- counter.increment('key1')
297
- expect(counter.get('key1')).toBe(1)
298
-
299
- counter.increment('key1')
300
- expect(counter.get('key1')).toBe(2)
301
-
302
- counter.increment('key2')
303
- expect(counter.get('key2')).toBe(1)
304
- })
305
-
306
- it('increments by custom amounts', () => {
307
- const counter = new Counter<string>()
308
-
309
- counter.increment('key1', 5)
310
- expect(counter.get('key1')).toBe(5)
311
-
312
- counter.increment('key1', 3)
313
- expect(counter.get('key1')).toBe(8)
314
- })
315
-
316
- it('decrements counts', () => {
317
- const counter = new Counter<string>()
318
-
319
- counter.set('key1', 10)
320
- counter.decrement('key1')
321
- expect(counter.get('key1')).toBe(9)
322
-
323
- counter.decrement('key1')
324
- expect(counter.get('key1')).toBe(8)
325
- })
326
-
327
- it('decrements by custom amounts', () => {
328
- const counter = new Counter<string>()
329
-
330
- counter.set('key1', 10)
331
- counter.decrement('key1', 3)
332
- expect(counter.get('key1')).toBe(7)
333
-
334
- counter.decrement('key1', 2)
335
- expect(counter.get('key1')).toBe(5)
336
- })
337
-
338
- it('allows negative counts', () => {
339
- const counter = new Counter<string>()
340
-
341
- counter.decrement('key1')
342
- expect(counter.get('key1')).toBe(-1)
343
-
344
- counter.decrement('key1', 5)
345
- expect(counter.get('key1')).toBe(-6)
346
- })
347
-
348
- it('accepts initial entries', () => {
349
- const initialEntries: [string, number][] = [
350
- ['a', 5],
351
- ['b', 10],
352
- ['c', 15],
353
- ]
354
- const counter = new Counter<string>(initialEntries)
355
-
356
- expect(counter.get('a')).toBe(5)
357
- expect(counter.get('b')).toBe(10)
358
- expect(counter.get('c')).toBe(15)
359
- expect(counter.get('d')).toBe(0)
360
- })
361
-
362
- it('works with all Map methods', () => {
363
- const counter = new Counter<string>()
364
-
365
- counter.increment('key1')
366
- counter.increment('key2', 2)
367
-
368
- expect(counter.size).toBe(2)
369
- expect(counter.has('key1')).toBe(true)
370
- expect([...counter.keys()]).toEqual(['key1', 'key2'])
371
- expect([...counter.values()]).toEqual([1, 2])
372
- })
373
- })
374
-
375
- describe('WeakCounter', () => {
376
- it('initializes counts to 0', () => {
377
- const counter = new WeakCounter<object>()
378
- const key1 = {}
379
- const key2 = {}
380
-
381
- expect(counter.get(key1)).toBe(0)
382
- expect(counter.get(key2)).toBe(0)
383
- })
384
-
385
- it('increments counts', () => {
386
- const counter = new WeakCounter<object>()
387
- const key = {}
388
-
389
- counter.increment(key)
390
- expect(counter.get(key)).toBe(1)
391
-
392
- counter.increment(key)
393
- expect(counter.get(key)).toBe(2)
394
- })
395
-
396
- it('increments by custom amounts', () => {
397
- const counter = new WeakCounter<object>()
398
- const key = {}
399
-
400
- counter.increment(key, 5)
401
- expect(counter.get(key)).toBe(5)
402
-
403
- counter.increment(key, 3)
404
- expect(counter.get(key)).toBe(8)
405
- })
406
-
407
- it('decrements counts', () => {
408
- const counter = new WeakCounter<object>()
409
- const key = {}
410
-
411
- counter.set(key, 10)
412
- counter.decrement(key)
413
- expect(counter.get(key)).toBe(9)
414
-
415
- counter.decrement(key)
416
- expect(counter.get(key)).toBe(8)
417
- })
418
-
419
- it('decrements by custom amounts', () => {
420
- const counter = new WeakCounter<object>()
421
- const key = {}
422
-
423
- counter.set(key, 10)
424
- counter.decrement(key, 3)
425
- expect(counter.get(key)).toBe(7)
426
-
427
- counter.decrement(key, 2)
428
- expect(counter.get(key)).toBe(5)
429
- })
430
-
431
- it('allows negative counts', () => {
432
- const counter = new WeakCounter<object>()
433
- const key = {}
434
-
435
- counter.decrement(key)
436
- expect(counter.get(key)).toBe(-1)
437
-
438
- counter.decrement(key, 5)
439
- expect(counter.get(key)).toBe(-6)
440
- })
441
-
442
- it('accepts initial entries', () => {
443
- const key1 = {}
444
- const key2 = {}
445
- const key3 = {}
446
- const initialEntries: [object, number][] = [
447
- [key1, 5],
448
- [key2, 10],
449
- [key3, 15],
450
- ]
451
- const counter = new WeakCounter<object>(initialEntries)
452
-
453
- expect(counter.get(key1)).toBe(5)
454
- expect(counter.get(key2)).toBe(10)
455
- expect(counter.get(key3)).toBe(15)
456
-
457
- const key4 = {}
458
- expect(counter.get(key4)).toBe(0)
459
- })
460
-
461
- it('works with WeakMap methods', () => {
462
- const counter = new WeakCounter<object>()
463
- const key1 = {}
464
- const key2 = {}
465
-
466
- counter.increment(key1)
467
- counter.increment(key2, 2)
468
-
469
- expect(counter.has(key1)).toBe(true)
470
- expect(counter.has(key2)).toBe(true)
471
-
472
- counter.delete(key1)
473
- expect(counter.has(key1)).toBe(false)
474
- })
475
- })
@@ -2,6 +2,68 @@
2
2
  * A map that automatically creates values for missing keys using a factory function.
3
3
  *
4
4
  * Similar to Python's [defaultdict](https://docs.python.org/3.13/library/collections.html#collections.defaultdict).
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * // Group items by category using arrays
9
+ * const groupByCategory = new DefaultMap<string, string[]>(() => [])
10
+ *
11
+ * groupByCategory.get('fruits').push('apple', 'banana')
12
+ * groupByCategory.get('vegetables').push('carrot')
13
+ * groupByCategory.get('fruits').push('orange')
14
+ *
15
+ * console.log(groupByCategory.get('fruits')) // ['apple', 'banana', 'orange']
16
+ * console.log(groupByCategory.get('vegetables')) // ['carrot']
17
+ * console.log(groupByCategory.get('dairy')) // [] (auto-created)
18
+ * ```
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * // Build a graph with adjacency lists
23
+ * const graph = new DefaultMap<string, Set<string>>(() => new Set())
24
+ *
25
+ * graph.get('A').add('B').add('C')
26
+ * graph.get('B').add('C').add('D')
27
+ * graph.get('C').add('D')
28
+ *
29
+ * console.log([...graph.get('A')]) // ['B', 'C']
30
+ * console.log([...graph.get('B')]) // ['C', 'D']
31
+ * console.log([...graph.get('E')]) // [] (auto-created empty set)
32
+ * ```
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * // Initialize with existing entries
37
+ * const scores = new DefaultMap<string, number>(
38
+ * () => 0,
39
+ * [
40
+ * ['Alice', 100],
41
+ * ['Bob', 85]
42
+ * ]
43
+ * )
44
+ *
45
+ * scores.set('Alice', scores.get('Alice') + 10) // 100 -> 110
46
+ * console.log(scores.get('Alice')) // 110
47
+ * console.log(scores.get('Bob')) // 85
48
+ * console.log(scores.get('Charlie')) // 0 (auto-created)
49
+ * ```
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * // Nested DefaultMaps for 2D data structures
54
+ * const matrix = new DefaultMap<number, DefaultMap<number, number>>(
55
+ * () => new DefaultMap<number, number>(() => 0)
56
+ * )
57
+ *
58
+ * matrix.get(0).set(0, 1)
59
+ * matrix.get(0).set(1, 2)
60
+ * matrix.get(1).set(1, 3)
61
+ *
62
+ * console.log(matrix.get(0).get(0)) // 1
63
+ * console.log(matrix.get(0).get(1)) // 2
64
+ * console.log(matrix.get(1).get(0)) // 0 (auto-created)
65
+ * console.log(matrix.get(2).get(3)) // 0 (both auto-created)
66
+ * ```
5
67
  */
6
68
  export class DefaultMap<K, V> extends Map<K, V> {
7
69
  private readonly defaultFactory: () => V
@@ -24,7 +86,93 @@ export class DefaultMap<K, V> extends Map<K, V> {
24
86
  /**
25
87
  * A weak map that automatically creates values for missing keys using a factory function.
26
88
  *
27
- * Similar to DefaultMap but uses WeakMap as the base, allowing garbage collection of keys.
89
+ * Similar to {@link DefaultMap} but uses WeakMap as the base, allowing garbage collection of keys.
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * // Store metadata for DOM elements without preventing garbage collection
94
+ * const elementMetadata = new DefaultWeakMap<HTMLElement, { clicks: number; hovers: number }>(
95
+ * () => ({ clicks: 0, hovers: 0 })
96
+ * )
97
+ *
98
+ * const button = document.querySelector('button')!
99
+ * const div = document.querySelector('div')!
100
+ *
101
+ * elementMetadata.get(button).clicks++
102
+ * elementMetadata.get(button).clicks++
103
+ * elementMetadata.get(div).hovers++
104
+ *
105
+ * console.log(elementMetadata.get(button)) // { clicks: 2, hovers: 0 }
106
+ * console.log(elementMetadata.get(div)) // { clicks: 0, hovers: 1 }
107
+ * // When elements are removed from DOM and not referenced,
108
+ * // their metadata can be garbage collected
109
+ * ```
110
+ *
111
+ * @example
112
+ * ```typescript
113
+ * // Cache computed properties for objects
114
+ * const computedCache = new DefaultWeakMap<object, Map<string, any>>(
115
+ * () => new Map()
116
+ * )
117
+ *
118
+ * function getOrCompute(obj: object, key: string, compute: () => any) {
119
+ * const cache = computedCache.get(obj)
120
+ * if (!cache.has(key)) {
121
+ * cache.set(key, compute())
122
+ * }
123
+ * return cache.get(key)
124
+ * }
125
+ *
126
+ * const user = { name: 'Alice', age: 30 }
127
+ * const displayName = getOrCompute(user, 'displayName', () => user.name.toUpperCase())
128
+ * const birthYear = getOrCompute(user, 'birthYear', () => new Date().getFullYear() - user.age)
129
+ *
130
+ * console.log(displayName) // 'ALICE'
131
+ * console.log(birthYear) // 1994 (or current year - 30)
132
+ * ```
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * // Initialize with existing entries
137
+ * const obj1 = {}
138
+ * const obj2 = {}
139
+ *
140
+ * const objectData = new DefaultWeakMap<object, string[]>(
141
+ * () => [],
142
+ * [
143
+ * [obj1, ['tag1', 'tag2']],
144
+ * [obj2, ['tag3']]
145
+ * ]
146
+ * )
147
+ *
148
+ * objectData.get(obj1).push('tag4')
149
+ * console.log(objectData.get(obj1)) // ['tag1', 'tag2', 'tag4']
150
+ * console.log(objectData.get(obj2)) // ['tag3']
151
+ *
152
+ * const obj3 = {}
153
+ * console.log(objectData.get(obj3)) // [] (auto-created)
154
+ * ```
155
+ *
156
+ * @example
157
+ * ```typescript
158
+ * // Track event listeners per element using both DefaultWeakMap and DefaultMap
159
+ * const eventListeners = new DefaultWeakMap<EventTarget, DefaultMap<string, Function[]>>(
160
+ * () => new DefaultMap<string, Function[]>(() => [])
161
+ * )
162
+ *
163
+ * function addListener(target: EventTarget, event: string, handler: Function) {
164
+ * eventListeners.get(target).get(event).push(handler)
165
+ * }
166
+ *
167
+ * const element = document.createElement('button')
168
+ * addListener(element, 'click', () => console.log('clicked'))
169
+ * addListener(element, 'click', () => console.log('also clicked'))
170
+ * addListener(element, 'hover', () => console.log('hovered'))
171
+ *
172
+ * console.log(eventListeners.get(element).get('click').length) // 2
173
+ * console.log(eventListeners.get(element).get('hover').length) // 1
174
+ * // No need for has() checks or null assertions - everything auto-initializes!
175
+ * ```
28
176
  */
29
177
  export class DefaultWeakMap<K extends WeakKey, V> extends WeakMap<K, V> {
30
178
  private readonly defaultFactory: () => V
@@ -46,53 +194,3 @@ export class DefaultWeakMap<K extends WeakKey, V> extends WeakMap<K, V> {
46
194
  return value
47
195
  }
48
196
  }
49
-
50
- /**
51
- * A map that counts occurrences of keys.
52
- *
53
- * Similar to Python's [Counter](https://docs.python.org/3.13/library/collections.html#collections.Counter).
54
- */
55
- export class Counter<K> extends DefaultMap<K, number> {
56
- constructor(iterable?: Iterable<readonly [K, number]>) {
57
- super(() => 0, iterable)
58
- }
59
-
60
- /**
61
- * Increments the count for a key by a given amount (default 1).
62
- */
63
- increment(key: K, amount = 1): void {
64
- this.set(key, this.get(key) + amount)
65
- }
66
-
67
- /**
68
- * Decrements the count for a key by a given amount (default 1).
69
- */
70
- decrement(key: K, amount = 1): void {
71
- this.set(key, this.get(key) - amount)
72
- }
73
- }
74
-
75
- /**
76
- * A weak map that counts occurrences of object keys.
77
- *
78
- * Similar to Counter but uses WeakMap as the base, allowing garbage collection of keys.
79
- */
80
- export class WeakCounter<K extends WeakKey> extends DefaultWeakMap<K, number> {
81
- constructor(entries?: readonly (readonly [K, number])[] | null) {
82
- super(() => 0, entries)
83
- }
84
-
85
- /**
86
- * Increments the count for a key by a given amount (default 1).
87
- */
88
- increment(key: K, amount = 1): void {
89
- this.set(key, this.get(key) + amount)
90
- }
91
-
92
- /**
93
- * Decrements the count for a key by a given amount (default 1).
94
- */
95
- decrement(key: K, amount = 1): void {
96
- this.set(key, this.get(key) - amount)
97
- }
98
- }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './checker'
2
- export { Counter, DefaultMap, DefaultWeakMap, WeakCounter } from './default-map'
2
+ export { Counter, WeakCounter } from './counter'
3
+ export { DefaultMap, DefaultWeakMap } from './default-map'
3
4
  export * from './dom'
4
5
  export { formatBytes } from './format-bytes'
5
6
  export { getId } from './get-id'
@@ -1,20 +1,12 @@
1
- import { describe, it, expect } from 'vitest'
1
+ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
2
2
 
3
- import {
4
- mapGroupBy,
5
- mapGroupByPolyfill,
6
- mapGroupByNative,
7
- } from './map-group-by'
3
+ import { mapGroupBy, mapGroupByPolyfill } from './map-group-by'
8
4
 
9
5
  const testCases = [
10
6
  { name: 'mapGroupBy', fn: mapGroupBy },
11
7
  { name: 'mapGroupByPolyfill', fn: mapGroupByPolyfill },
12
8
  ]
13
9
 
14
- if (!!Map.groupBy) {
15
- testCases.push({ name: 'mapGroupByNative', fn: mapGroupByNative })
16
- }
17
-
18
10
  describe.each(testCases)('$name', ({ fn }) => {
19
11
  it('groups items by key', () => {
20
12
  const items = [1, 2, 3, 4, 5, 6]
@@ -76,3 +68,24 @@ describe.each(testCases)('$name', ({ fn }) => {
76
68
  expect(result.get(1)).toEqual([1, 3, 5])
77
69
  })
78
70
  })
71
+
72
+ describe('mapGroupBy', () => {
73
+ beforeEach(() => {
74
+ if ('groupBy' in Map) {
75
+ // @ts-expect-error - spy
76
+ vi.spyOn(Map, 'groupBy', 'get').mockReturnValueOnce(undefined)
77
+ }
78
+ })
79
+
80
+ afterEach(() => {
81
+ vi.restoreAllMocks()
82
+ })
83
+
84
+ it('falls back to polyfill when Map.groupBy is not available', () => {
85
+ const items = [1, 2, 3, 4, 5, 6]
86
+ const result = mapGroupBy(items, (item) => item % 2)
87
+
88
+ expect(result.get(0)).toEqual([2, 4, 6])
89
+ expect(result.get(1)).toEqual([1, 3, 5])
90
+ })
91
+ })
@@ -21,23 +21,15 @@ export function mapGroupByPolyfill<K, T>(
21
21
  }
22
22
 
23
23
  /**
24
- * @internal
25
- */
26
- export function mapGroupByNative<K, T>(
27
- items: Iterable<T>,
28
- keySelector: (item: T, index: number) => K,
29
- ): Map<K, T[]> {
30
- return Map.groupBy(items, keySelector)
31
- }
32
-
33
- const hasMapGroupBy: boolean = /* @__PURE__ */ (() => !!Map.groupBy)()
34
-
35
- /**
36
- * A polyfill for the `Map.groupBy` static method.
24
+ * A polyfill for the [`Map.groupBy()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/groupBy) static method.
37
25
  *
38
26
  * @public
39
27
  */
40
- export const mapGroupBy: <K, T>(
28
+ export function mapGroupBy<K, T>(
41
29
  items: Iterable<T>,
42
30
  keySelector: (item: T, index: number) => K,
43
- ) => Map<K, T[]> = hasMapGroupBy ? mapGroupByNative : mapGroupByPolyfill
31
+ ): Map<K, T[]> {
32
+ return Map.groupBy
33
+ ? Map.groupBy(items, keySelector)
34
+ : mapGroupByPolyfill(items, keySelector)
35
+ }
package/src/map-values.ts CHANGED
@@ -35,7 +35,7 @@ export function mapValues<ValueIn, ValueOut>(
35
35
  object: Record<string, ValueIn>,
36
36
  callback: (value: ValueIn, key: string) => ValueOut,
37
37
  ): Record<string, ValueOut> {
38
- let result = {} as Record<string, ValueOut>
38
+ const result = {} as Record<string, ValueOut>
39
39
  for (const [key, value] of Object.entries(object)) {
40
40
  result[key] = callback(value, key)
41
41
  }
@@ -17,13 +17,16 @@ export type ObjectEntries<T extends Record<string, any>> = {
17
17
  }[keyof T]
18
18
 
19
19
  /**
20
- * A type-safe wrapper around `Object.entries()` that preserves the exact types of object keys
21
- * and values. Unlike the standard `Object.entries()` which returns `[string, any][]`, this
22
- * function returns an array of tuples where each tuple is precisely typed according to the
23
- * input object's structure.
20
+ * A type-safe wrapper around
21
+ * [`Object.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries)
22
+ * that preserves the exact types of object keys and values. Unlike the standard
23
+ * `Object.entries()` which returns `[string, any][]`, this function returns an
24
+ * array of tuples where each tuple is precisely typed according to the input
25
+ * object's structure.
24
26
  *
25
- * This is particularly useful when working with objects that have known, fixed property types
26
- * and you want to maintain type safety when iterating over entries.
27
+ * This is particularly useful when working with objects that have known, fixed
28
+ * property types and you want to maintain type safety when iterating over
29
+ * entries.
27
30
  *
28
31
  * @example
29
32
  * ```typescript
@@ -53,6 +56,8 @@ export type ObjectEntries<T extends Record<string, any>> = {
53
56
  *
54
57
  * @public
55
58
  */
56
- export const objectEntries: <T extends Record<string, any>>(
59
+ export function objectEntries<T extends Record<string, any>>(
57
60
  obj: T,
58
- ) => ObjectEntries<T>[] = Object.entries
61
+ ): ObjectEntries<T>[] {
62
+ return Object.entries(obj)
63
+ }
@@ -1,20 +1,12 @@
1
- import { describe, it, expect } from 'vitest'
1
+ import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest'
2
2
 
3
- import {
4
- objectGroupBy,
5
- objectGroupByPolyfill,
6
- objectGroupByNative,
7
- } from './object-group-by'
3
+ import { objectGroupBy, objectGroupByPolyfill } from './object-group-by'
8
4
 
9
5
  const testCases = [
10
6
  { name: 'objectGroupBy', fn: objectGroupBy },
11
7
  { name: 'objectGroupByPolyfill', fn: objectGroupByPolyfill },
12
8
  ]
13
9
 
14
- if (!!Object.groupBy) {
15
- testCases.push({ name: 'objectGroupByNative', fn: objectGroupByNative })
16
- }
17
-
18
10
  describe.each(testCases)('$name', ({ fn }) => {
19
11
  it('groups items by key', () => {
20
12
  const items = [1, 2, 3, 4, 5, 6]
@@ -84,3 +76,26 @@ describe.each(testCases)('$name', ({ fn }) => {
84
76
  expect(result.odd).toEqual([1, 3, 5])
85
77
  })
86
78
  })
79
+
80
+ describe('objectGroupBy', () => {
81
+ beforeEach(() => {
82
+ if ('groupBy' in Object) {
83
+ // @ts-expect-error - spy
84
+ vi.spyOn(Object, 'groupBy', 'get').mockReturnValueOnce(undefined)
85
+ }
86
+ })
87
+
88
+ afterEach(() => {
89
+ vi.restoreAllMocks()
90
+ })
91
+
92
+ it('falls back to polyfill when Object.groupBy is not available', () => {
93
+ const items = [1, 2, 3, 4, 5, 6]
94
+ const result = objectGroupBy(items, (item) =>
95
+ item % 2 === 0 ? 'even' : 'odd',
96
+ )
97
+
98
+ expect(result.even).toEqual([2, 4, 6])
99
+ expect(result.odd).toEqual([1, 3, 5])
100
+ })
101
+ })
@@ -21,25 +21,15 @@ export function objectGroupByPolyfill<K extends PropertyKey, T>(
21
21
  }
22
22
 
23
23
  /**
24
- * @internal
25
- */
26
- export function objectGroupByNative<K extends PropertyKey, T>(
27
- items: Iterable<T>,
28
- keySelector: (item: T, index: number) => K,
29
- ): Partial<Record<K, T[]>> {
30
- return Object.groupBy(items, keySelector)
31
- }
32
-
33
- const hasObjectGroupBy: boolean = /* @__PURE__ */ (() => !!Object.groupBy)()
34
-
35
- /**
36
- * A polyfill for the `Object.groupBy` static method.
24
+ * A polyfill for the [`Object.groupBy()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/groupBy) static method.
37
25
  *
38
26
  * @public
39
27
  */
40
- export const objectGroupBy: <K extends PropertyKey, T>(
28
+ export function objectGroupBy<K extends PropertyKey, T>(
41
29
  items: Iterable<T>,
42
30
  keySelector: (item: T, index: number) => K,
43
- ) => Partial<Record<K, T[]>> = hasObjectGroupBy
44
- ? objectGroupByNative
45
- : objectGroupByPolyfill
31
+ ): Partial<Record<K, T[]>> {
32
+ return Object.groupBy
33
+ ? Object.groupBy(items, keySelector)
34
+ : objectGroupByPolyfill(items, keySelector)
35
+ }
package/src/once.test.ts CHANGED
@@ -1,8 +1,6 @@
1
1
  // @vitest-environment node
2
2
 
3
- import path from 'node:path'
4
-
5
- import { describe, it, expect, vi } from 'vitest'
3
+ import { describe, expect, it, vi } from 'vitest'
6
4
 
7
5
  import { once } from './once'
8
6
 
@@ -25,31 +23,4 @@ describe('once', () => {
25
23
  const getValue = once(() => value)
26
24
  expect(getValue()).toBe(value)
27
25
  })
28
-
29
- it('can tree-shake', async () => {
30
- const esbuild = await import('esbuild')
31
- const cwd = import.meta.dirname
32
- const input = path.join(cwd, 'once-stub-2.ts')
33
-
34
- const result = await esbuild.build({
35
- entryPoints: [input],
36
- format: 'esm',
37
- platform: 'neutral',
38
- minify: true,
39
- bundle: true,
40
- write: false,
41
- absWorkingDir: cwd,
42
- })
43
-
44
- const files = result.outputFiles.map((file) => {
45
- return file.text
46
- })
47
-
48
- expect(files).toMatchInlineSnapshot(`
49
- [
50
- "function t(n){let o=!1,e;return()=>(o||(e=n(),o=!0,n=void 0),e)}function r(){console.log("fn1")}var u=t(r);function f(){console.log("fn2")}f();
51
- ",
52
- ]
53
- `)
54
- })
55
26
  })
package/src/sleep.ts CHANGED
@@ -1,5 +1,12 @@
1
1
  /**
2
- * Sleep for a given number of milliseconds.
2
+ * Returns a Promise that resolves after a specified number of milliseconds.
3
+ *
4
+ * @param ms - The number of milliseconds to wait.
5
+ *
6
+ * @example
7
+ * ```js
8
+ * await sleep(1000) // Wait 1 second
9
+ * ```
3
10
  */
4
11
  export function sleep(ms: number): Promise<void> {
5
12
  return new Promise((resolve) => setTimeout(resolve, ms))