feat(stdlib): new modules + eslint/tsconfig migration

- Add array/async/etc. modules and type tests; migrate to eslint flat config
  and composite tsconfig (vitest typecheck enabled).
- Fix PubSub.emit to snapshot listeners before iterating (stable EventEmitter
  semantics; avoids invoking listeners added during the same emit).
This commit is contained in:
2026-06-07 16:29:08 +07:00
parent 008d85a8fd
commit 96f4cba4a8
118 changed files with 3511 additions and 240 deletions
@@ -0,0 +1,17 @@
import { describe, expectTypeOf, it } from 'vitest';
import { partition } from '.';
describe('partition', () => {
it('returns a tuple of two arrays of the element type', () => {
const result = partition([1, 2, 3], n => n > 1);
expectTypeOf(result).toEqualTypeOf<[number[], number[]]>();
});
it('narrows both partitions with a type guard', () => {
const mixed: Array<string | number> = ['a', 1];
const result = partition(mixed, (v): v is string => typeof v === 'string');
expectTypeOf(result).toEqualTypeOf<[string[], number[]]>();
});
});
@@ -0,0 +1,29 @@
import { describe, expect, it } from 'vitest';
import { partition } from '.';
describe('partition', () => {
it('split by a predicate into [matching, rest]', () => {
expect(partition([1, 2, 3, 4], n => n % 2 === 0)).toEqual([[2, 4], [1, 3]]);
});
it('preserve order within each partition', () => {
expect(partition([5, 1, 4, 2, 3], n => n > 2)).toEqual([[5, 4, 3], [1, 2]]);
});
it('pass the index to the predicate', () => {
expect(partition(['a', 'b', 'c', 'd'], (_, i) => i < 2)).toEqual([['a', 'b'], ['c', 'd']]);
});
it('handle all-matching and none-matching', () => {
expect(partition([1, 2, 3], () => true)).toEqual([[1, 2, 3], []]);
expect(partition([1, 2, 3], () => false)).toEqual([[], [1, 2, 3]]);
});
it('work with a type guard', () => {
const mixed: Array<string | number> = ['a', 1, 'b', 2];
const [strings, numbers] = partition(mixed, (v): v is string => typeof v === 'string');
expect(strings).toEqual(['a', 'b']);
expect(numbers).toEqual([1, 2]);
});
});
+43
View File
@@ -0,0 +1,43 @@
/**
* @name partition
* @category Arrays
* @description Splits an array into two: elements that satisfy the predicate and those that do not
*
* @param {Value[]} array - The array to split
* @param {(item: Value, index: number) => boolean} predicate - Decides which partition an element belongs to
* @returns {[Value[], Value[]]} A tuple of `[matching, rest]`
*
* @example
* partition([1, 2, 3, 4], n => n % 2 === 0) // => [[2, 4], [1, 3]]
*
* @example
* const [strings, others] = partition(mixed, (v): v is string => typeof v === 'string');
*
* @since 0.0.10
*/
export function partition<Value, Matched extends Value>(
array: Value[],
predicate: (item: Value, index: number) => item is Matched,
): [Matched[], Array<Exclude<Value, Matched>>];
export function partition<Value>(
array: Value[],
predicate: (item: Value, index: number) => boolean,
): [Value[], Value[]];
export function partition<Value>(
array: Value[],
predicate: (item: Value, index: number) => boolean,
): [Value[], Value[]] {
const matched: Value[] = [];
const rest: Value[] = [];
for (let i = 0; i < array.length; i++) {
const item = array[i]!;
if (predicate(item, i))
matched.push(item);
else
rest.push(item);
}
return [matched, rest];
}