@ocavue/utils 1.2.0 → 1.3.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.
- package/dist/index.d.ts +43 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +80 -306
- package/dist/index.js.map +1 -1
- package/package.json +16 -13
- package/src/__snapshots__/e2e.test.ts.snap +247 -0
- package/src/default-map.test.ts +290 -1
- package/src/default-map.ts +76 -0
- package/src/e2e.test.ts +36 -0
- package/src/get-id.test.ts +20 -1
- package/src/get-id.ts +15 -1
- package/src/index.ts +1 -1
- package/src/map-group-by.ts +3 -1
- package/src/object-group-by.ts +3 -1
- package/src/once-stub-1.ts +13 -0
- package/src/once-stub-2.ts +3 -0
- package/src/once.test.ts +29 -0
package/src/default-map.test.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { describe, it, expect } from 'vitest'
|
|
4
4
|
|
|
5
|
-
import { DefaultMap } from './default-map'
|
|
5
|
+
import { Counter, DefaultMap, DefaultWeakMap, WeakCounter } from './default-map'
|
|
6
6
|
|
|
7
7
|
describe('DefaultMap', () => {
|
|
8
8
|
it('creates default values for missing keys', () => {
|
|
@@ -184,3 +184,292 @@ describe('DefaultMap', () => {
|
|
|
184
184
|
expect(map.get('group2').get('item1')).toBe(0)
|
|
185
185
|
})
|
|
186
186
|
})
|
|
187
|
+
|
|
188
|
+
describe('DefaultWeakMap', () => {
|
|
189
|
+
it('creates default values for missing keys', () => {
|
|
190
|
+
const map = new DefaultWeakMap<object, number>(() => 0)
|
|
191
|
+
const key1 = {}
|
|
192
|
+
const key2 = {}
|
|
193
|
+
|
|
194
|
+
expect(map.get(key1)).toBe(0)
|
|
195
|
+
expect(map.get(key2)).toBe(0)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('returns existing values for set keys', () => {
|
|
199
|
+
const map = new DefaultWeakMap<object, number>(() => 0)
|
|
200
|
+
const key = {}
|
|
201
|
+
|
|
202
|
+
map.set(key, 42)
|
|
203
|
+
expect(map.get(key)).toBe(42)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('stores the default value when accessing missing key', () => {
|
|
207
|
+
const map = new DefaultWeakMap<object, number>(() => 5)
|
|
208
|
+
const key = {}
|
|
209
|
+
|
|
210
|
+
const value = map.get(key)
|
|
211
|
+
expect(value).toBe(5)
|
|
212
|
+
expect(map.has(key)).toBe(true)
|
|
213
|
+
expect(map.get(key)).toBe(5)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('works with array factory', () => {
|
|
217
|
+
const map = new DefaultWeakMap<object, string[]>(() => [])
|
|
218
|
+
const key1 = {}
|
|
219
|
+
const key2 = {}
|
|
220
|
+
|
|
221
|
+
map.get(key1).push('item1')
|
|
222
|
+
map.get(key1).push('item2')
|
|
223
|
+
map.get(key2).push('item3')
|
|
224
|
+
|
|
225
|
+
expect(map.get(key1)).toEqual(['item1', 'item2'])
|
|
226
|
+
expect(map.get(key2)).toEqual(['item3'])
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
it('accepts initial entries', () => {
|
|
230
|
+
const key1 = {}
|
|
231
|
+
const key2 = {}
|
|
232
|
+
const key3 = {}
|
|
233
|
+
const initialEntries: [object, number][] = [
|
|
234
|
+
[key1, 1],
|
|
235
|
+
[key2, 2],
|
|
236
|
+
[key3, 3],
|
|
237
|
+
]
|
|
238
|
+
const map = new DefaultWeakMap<object, number>(() => 0, initialEntries)
|
|
239
|
+
|
|
240
|
+
expect(map.get(key1)).toBe(1)
|
|
241
|
+
expect(map.get(key2)).toBe(2)
|
|
242
|
+
expect(map.get(key3)).toBe(3)
|
|
243
|
+
|
|
244
|
+
const key4 = {}
|
|
245
|
+
expect(map.get(key4)).toBe(0)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it('calls factory function only when key is missing', () => {
|
|
249
|
+
let callCount = 0
|
|
250
|
+
const map = new DefaultWeakMap<object, number>(() => {
|
|
251
|
+
callCount++
|
|
252
|
+
return 42
|
|
253
|
+
})
|
|
254
|
+
const existing = {}
|
|
255
|
+
const newKey = {}
|
|
256
|
+
|
|
257
|
+
map.set(existing, 100)
|
|
258
|
+
|
|
259
|
+
map.get(existing)
|
|
260
|
+
expect(callCount).toBe(0)
|
|
261
|
+
|
|
262
|
+
map.get(newKey)
|
|
263
|
+
expect(callCount).toBe(1)
|
|
264
|
+
|
|
265
|
+
map.get(newKey)
|
|
266
|
+
expect(callCount).toBe(1)
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
it('works with delete method', () => {
|
|
270
|
+
const map = new DefaultWeakMap<object, number>(() => 10)
|
|
271
|
+
const key = {}
|
|
272
|
+
|
|
273
|
+
map.get(key)
|
|
274
|
+
expect(map.has(key)).toBe(true)
|
|
275
|
+
|
|
276
|
+
map.delete(key)
|
|
277
|
+
expect(map.has(key)).toBe(false)
|
|
278
|
+
|
|
279
|
+
const value = map.get(key)
|
|
280
|
+
expect(value).toBe(10)
|
|
281
|
+
expect(map.has(key)).toBe(true)
|
|
282
|
+
})
|
|
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
|
+
})
|
package/src/default-map.ts
CHANGED
|
@@ -20,3 +20,79 @@ export class DefaultMap<K, V> extends Map<K, V> {
|
|
|
20
20
|
return value
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A weak map that automatically creates values for missing keys using a factory function.
|
|
26
|
+
*
|
|
27
|
+
* Similar to DefaultMap but uses WeakMap as the base, allowing garbage collection of keys.
|
|
28
|
+
*/
|
|
29
|
+
export class DefaultWeakMap<K extends WeakKey, V> extends WeakMap<K, V> {
|
|
30
|
+
private readonly defaultFactory: () => V
|
|
31
|
+
|
|
32
|
+
constructor(
|
|
33
|
+
defaultFactory: () => V,
|
|
34
|
+
entries?: readonly (readonly [K, V])[] | null,
|
|
35
|
+
) {
|
|
36
|
+
super(entries)
|
|
37
|
+
this.defaultFactory = defaultFactory
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override get(key: K): V {
|
|
41
|
+
if (this.has(key)) {
|
|
42
|
+
return super.get(key)!
|
|
43
|
+
}
|
|
44
|
+
const value = this.defaultFactory()
|
|
45
|
+
this.set(key, value)
|
|
46
|
+
return value
|
|
47
|
+
}
|
|
48
|
+
}
|
|
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/e2e.test.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
|
|
6
|
+
import { x } from 'tinyexec'
|
|
7
|
+
import { glob } from 'tinyglobby'
|
|
8
|
+
import { describe, it, expect, beforeAll } from 'vitest'
|
|
9
|
+
|
|
10
|
+
const ROOT_DIR = path.join(import.meta.dirname, '..')
|
|
11
|
+
const E2E_OUT_DIR = path.join(ROOT_DIR, 'e2e', 'dist')
|
|
12
|
+
|
|
13
|
+
describe('e2e', () => {
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
await x('pnpm', ['-w', 'build'], {
|
|
16
|
+
nodeOptions: { cwd: ROOT_DIR, stdio: 'inherit' },
|
|
17
|
+
throwOnError: true,
|
|
18
|
+
})
|
|
19
|
+
await x('pnpm', ['--filter', 'e2e', 'run', 'build'], {
|
|
20
|
+
nodeOptions: { cwd: ROOT_DIR, stdio: 'inherit' },
|
|
21
|
+
throwOnError: true,
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('bundler outputs match snapshot', async () => {
|
|
26
|
+
const files = await glob('**/*', { cwd: E2E_OUT_DIR, onlyFiles: true })
|
|
27
|
+
const output = ['']
|
|
28
|
+
|
|
29
|
+
for (const file of files.sort()) {
|
|
30
|
+
const content = fs.readFileSync(path.join(E2E_OUT_DIR, file), 'utf-8')
|
|
31
|
+
output.push('#'.repeat(80), file, '-'.repeat(80), content, '')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
expect(output.join('\n')).toMatchSnapshot()
|
|
35
|
+
})
|
|
36
|
+
})
|
package/src/get-id.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
2
|
|
|
3
|
-
import { getId } from './get-id'
|
|
3
|
+
import { getId, setMaxSafeInteger } from './get-id'
|
|
4
4
|
|
|
5
5
|
describe('getId', () => {
|
|
6
6
|
it('returns a positive number', () => {
|
|
@@ -11,4 +11,23 @@ describe('getId', () => {
|
|
|
11
11
|
it('returns different values on consecutive calls', () => {
|
|
12
12
|
expect(getId()).not.toBe(getId())
|
|
13
13
|
})
|
|
14
|
+
|
|
15
|
+
it('never exceeds the configured maximum', () => {
|
|
16
|
+
const max = 5
|
|
17
|
+
setMaxSafeInteger(max)
|
|
18
|
+
let prevId = -1
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
for (let i = 0; i < max * 3; i += 1) {
|
|
22
|
+
const id = getId()
|
|
23
|
+
expect(id).toBeLessThan(max)
|
|
24
|
+
expect(id).toBeGreaterThanOrEqual(1)
|
|
25
|
+
expect(id).not.toBe(prevId)
|
|
26
|
+
|
|
27
|
+
prevId = id
|
|
28
|
+
}
|
|
29
|
+
} finally {
|
|
30
|
+
setMaxSafeInteger(Number.MAX_SAFE_INTEGER)
|
|
31
|
+
}
|
|
32
|
+
})
|
|
14
33
|
})
|
package/src/get-id.ts
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
let id = 0
|
|
2
2
|
|
|
3
|
+
let maxSafeInteger = Number.MAX_SAFE_INTEGER
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sets the maximum safe integer for the id generator. Only for testing purposes.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export function setMaxSafeInteger(max: number) {
|
|
11
|
+
maxSafeInteger = max
|
|
12
|
+
}
|
|
13
|
+
|
|
3
14
|
/**
|
|
4
15
|
* Generates a unique positive integer.
|
|
5
16
|
*/
|
|
6
17
|
export function getId(): number {
|
|
7
|
-
id
|
|
18
|
+
id++
|
|
19
|
+
if (id >= maxSafeInteger) {
|
|
20
|
+
id = 1
|
|
21
|
+
}
|
|
8
22
|
return id
|
|
9
23
|
}
|
package/src/index.ts
CHANGED
package/src/map-group-by.ts
CHANGED
|
@@ -30,6 +30,8 @@ export function mapGroupByNative<K, T>(
|
|
|
30
30
|
return Map.groupBy(items, keySelector)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
const hasMapGroupBy: boolean = /* @__PURE__ */ (() => !!Map.groupBy)()
|
|
34
|
+
|
|
33
35
|
/**
|
|
34
36
|
* A polyfill for the `Map.groupBy` static method.
|
|
35
37
|
*
|
|
@@ -38,4 +40,4 @@ export function mapGroupByNative<K, T>(
|
|
|
38
40
|
export const mapGroupBy: <K, T>(
|
|
39
41
|
items: Iterable<T>,
|
|
40
42
|
keySelector: (item: T, index: number) => K,
|
|
41
|
-
) => Map<K, T[]> =
|
|
43
|
+
) => Map<K, T[]> = hasMapGroupBy ? mapGroupByNative : mapGroupByPolyfill
|
package/src/object-group-by.ts
CHANGED
|
@@ -30,6 +30,8 @@ export function objectGroupByNative<K extends PropertyKey, T>(
|
|
|
30
30
|
return Object.groupBy(items, keySelector)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
const hasObjectGroupBy: boolean = /* @__PURE__ */ (() => !!Object.groupBy)()
|
|
34
|
+
|
|
33
35
|
/**
|
|
34
36
|
* A polyfill for the `Object.groupBy` static method.
|
|
35
37
|
*
|
|
@@ -38,6 +40,6 @@ export function objectGroupByNative<K extends PropertyKey, T>(
|
|
|
38
40
|
export const objectGroupBy: <K extends PropertyKey, T>(
|
|
39
41
|
items: Iterable<T>,
|
|
40
42
|
keySelector: (item: T, index: number) => K,
|
|
41
|
-
) => Partial<Record<K, T[]>> =
|
|
43
|
+
) => Partial<Record<K, T[]>> = hasObjectGroupBy
|
|
42
44
|
? objectGroupByNative
|
|
43
45
|
: objectGroupByPolyfill
|
package/src/once.test.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// @vitest-environment node
|
|
2
2
|
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
3
5
|
import { describe, it, expect, vi } from 'vitest'
|
|
4
6
|
|
|
5
7
|
import { once } from './once'
|
|
@@ -23,4 +25,31 @@ describe('once', () => {
|
|
|
23
25
|
const getValue = once(() => value)
|
|
24
26
|
expect(getValue()).toBe(value)
|
|
25
27
|
})
|
|
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
|
+
})
|
|
26
55
|
})
|