UNPKG

async-collection-utils

Version:

A well tested and typed collection of map, forEach, reduce, filter etc. utility functions supporting arrays, sets, maps, plain objects, iterators in both async and synchronous versions.

594 lines (523 loc) 19.8 kB
# aider chat started at 2025-07-07 21:04:37 > You can skip this check with --no-gitignore > Add .aider* to .gitignore (recommended)? (Y)es/(N)o [Yes]: y > Added .aider* to .gitignore > /Users/lukasz/.local/bin/aider --model o3 --reasoning-effort high --weak-model gpt-4.1-mini --watch --no-auto-commits > Warning: Streaming is not supported by o3. Disabling streaming. > Error: Read-only file /Users/lukasz/Dev/_NEW/async-collection-utils/aider-conventions.md does not exist. Skipping. > Aider v0.85.1 > Main model: o3 with diff edit format, reasoning high > Weak model: gpt-4.1-mini > Git repo: .git with 30 files > Repo-map: using 4096 tokens, auto refresh > Added ../../../aider-conventions.md to the chat (read-only). #### /add src/asyncMap.ts > Added src/asyncMap.ts to the chat #### /add src/asyncMap.test.ts > Added src/asyncMap.test.ts to the chat #### /code implement asyncFilter, try to follow as closely as possible a pattern set in asyncMap and asyncFlatMap utilities #### implement asyncFilter, try to follow as closely as possible a pattern set in asyncMap and asyncFlatMap utilities src/asyncFilter.ts ````typescript <<<<<<< SEARCH ======= import { Break, CustomAsyncIterable, CustomIterable, isPlainObject, LastClass, TypedArray, } from "./shared"; /** * Asynchronously filters different kinds of iterables/collections. * The predicate can short-circuit by returning `Break` or * keep the current item and stop by returning `Last(value)`. */ /* ──────────────────────────── Array ──────────────────────────── */ export async function asyncFilter<TCollection extends unknown[]>( array: TCollection, predicate: ( item: TCollection extends Array<infer TValue> ? TValue : never, index: number, arr: TCollection, ) => Promise< | boolean | typeof Break | LastClass<TCollection extends Array<infer TValue> ? TValue : never> >, ): Promise<Array<TCollection[number]>>; /* ───────────────────────────── Set ───────────────────────────── */ export async function asyncFilter<TCollection extends Set<unknown>>( set: TCollection, predicate: ( item: TCollection extends Set<infer TValue> ? TValue : never, index: number, set: TCollection, ) => Promise< | boolean | typeof Break | LastClass<TCollection extends Set<infer TValue> ? TValue : never> >, ): Promise<Set<TCollection extends Set<infer TValue> ? TValue : never>>; /* ───────────────────────────── Map ───────────────────────────── */ export async function asyncFilter<TCollection extends Map<unknown, unknown>>( map: TCollection, predicate: ( value: TCollection extends Map<unknown, infer TValue> ? TValue : never, key: TCollection extends Map<infer TKey, unknown> ? TKey : never, map: TCollection, ) => Promise< | boolean | typeof Break | LastClass<TCollection extends Map<unknown, infer TValue> ? TValue : never> >, ): Promise< Map< TCollection extends Map<infer TKey, unknown> ? TKey : never, TCollection extends Map<unknown, infer TValue> ? TValue : never > >; /* ───────────────────────── TypedArray ────────────────────────── */ export async function asyncFilter<TCollection extends TypedArray>( typedArray: TCollection, predicate: ( item: TCollection[number], index: number, arr: TCollection, ) => Promise<boolean | typeof Break | LastClass<TCollection[number]>>, ): Promise<TCollection>; /* ──────────────────────── Custom Iterable ────────────────────── */ export async function asyncFilter< TCollection extends CustomIterable<unknown, unknown>, >( iterable: TCollection, predicate: ( item: TCollection extends CustomIterable<infer TValue, unknown> ? TValue : never, index: number, iterable: TCollection, ) => Promise< | boolean | typeof Break | LastClass< TCollection extends CustomIterable<infer TValue, unknown> ? TValue : never > >, ): Promise< AsyncGenerator< TCollection extends CustomIterable<infer TValue, unknown> ? TValue : never > >; /* ────────────────────── Custom AsyncIterable ─────────────────── */ export async function asyncFilter< TCollection extends CustomAsyncIterable<unknown, unknown>, >( asyncIterable: TCollection, predicate: ( item: TCollection extends CustomAsyncIterable<infer TValue, unknown> ? TValue : never, index: number, iterable: TCollection, ) => Promise< | boolean | typeof Break | LastClass< TCollection extends CustomAsyncIterable<infer TValue, unknown> ? TValue : never > >, ): Promise< AsyncGenerator< TCollection extends CustomAsyncIterable<infer TValue, unknown> ? TValue : never > >; /* ───────────────────────── Plain Object ──────────────────────── */ export async function asyncFilter< TCollection extends Record<string | number | symbol, unknown>, >( obj: TCollection, predicate: ( value: TCollection[keyof TCollection], key: keyof TCollection & string, object: TCollection, ) => Promise< | boolean | typeof Break | LastClass<TCollection[keyof TCollection]> >, ): Promise<{ [K in keyof TCollection]: TCollection[K] }>; /* ───────────────────────── Default impl ──────────────────────── */ export async function asyncFilter( iterable: unknown, predicate: (...args: any[]) => Promise<unknown>, ): Promise<unknown> { /* ───── Array ───── */ if (Array.isArray(iterable)) { const result: unknown[] = []; for (let i = 0; i < iterable.length; i++) { const decision = await predicate(iterable[i], i, iterable); if (decision === Break) break; if (decision instanceof LastClass) { result.push(decision.value); break; } if (decision) result.push(iterable[i]); } return result; } /* ───── Set ───── */ if (iterable instanceof Set) { const result = new Set(); let i = 0; for (const item of iterable) { const decision = await predicate(item, i++, iterable); if (decision === Break) break; if (decision instanceof LastClass) { result.add(decision.value); break; } if (decision) result.add(item); } return result; } /* ───── Map ───── */ if (iterable instanceof Map) { const result = new Map(); for (const [key, value] of iterable) { const decision = await predicate(value, key, iterable); if (decision === Break) break; if (decision instanceof LastClass) { result.set(key, decision.value); break; } if (decision) result.set(key, value); } return result; } /* ───── TypedArray ───── */ if (ArrayBuffer.isView(iterable)) { const Ctor = iterable.constructor as any; const buffer: unknown[] = []; let i = 0; for (const value of iterable as TypedArray) { const decision = await predicate(value, i++, iterable); if (decision === Break) break; if (decision instanceof LastClass) { buffer.push(decision.value); break; } if (decision) buffer.push(value); } return new Ctor(buffer); } /* ───── Plain Object ───── */ if (isPlainObject(iterable)) { const result: Record<string, unknown> = {}; for (const key of Object.keys(iterable)) { const decision = await predicate(iterable[key], key, iterable); if (decision === Break) break; if (decision instanceof LastClass) { result[key] = decision.value; break; } if (decision) result[key] = iterable[key]; } return result; } /* ───── (Async)Iterators ───── */ async function* generator() { let i = 0; for await (const item of iterable as Iterable<unknown>) { const decision = await predicate(item, i++, iterable); if (decision === Break) break; if (decision instanceof LastClass) { yield decision.value; break; } if (decision) yield item; } } return generator(); } >>>>>>> REPLACE ```` Suggested shell commands: ```bash bun test ``` > Tokens: 9.7k sent, 3.9k received. Cost: $0.05 message, $0.05 session. > src/asyncFilter.ts > Create new file? (Y)es/(N)o [Yes]: y > Applied edit to src/asyncFilter.ts > bun test > Run shell command? (Y)es/(N)o/(D)on't ask again [Yes]: y > Running bun test > Add command output to the chat? (Y)es/(N)o/(D)on't ask again [Yes]: y > Added 133 lines of output to the chat. > > > ^C again to exit #### /exit # aider chat started at 2025-07-07 21:15:47 > /Users/lukasz/.local/bin/aider --model o3 --reasoning-effort high --weak-model gpt-4.1-mini --watch --no-auto-commits > Warning: Streaming is not supported by o3. Disabling streaming. > Error: Read-only file /Users/lukasz/Dev/_NEW/async-collection-utils/aider-conventions.md does not exist. Skipping. > Aider v0.85.1 > Main model: o3 with diff edit format, reasoning high > Weak model: gpt-4.1-mini > Git repo: .git with 31 files > Repo-map: using 4096 tokens, auto refresh > Added ../../../aider-conventions.md to the chat (read-only). #### /add src/asyncFilter.ts src/asyncMap.ts src/asyncMap.test.ts > Added src/asyncFilter.ts to the chat > Added src/asyncMap.test.ts to the chat > Added src/asyncMap.ts to the chat #### /code write tests for asyncFilter function. Follow very closely the pattern in existing tests e.g. in asyncMap.test.ts. #### write tests for asyncFilter function. Follow very closely the pattern in existing tests e.g. in asyncMap.test.ts. src/asyncFilter.test.ts ````typescript <<<<<<< SEARCH ======= import { asyncFilter } from "./asyncFilter"; import { Break, Last } from "./shared"; describe("asyncFilter", () => { /* ───────────────────────── Array ───────────────────────── */ describe("array", () => { it("should filter values", async () => { const result = await asyncFilter([1, 2, 3], async (value) => value % 2 === 0); expect(result).toStrictEqual([2]); }); it("should break on encountering Break", async () => { const result = await asyncFilter([1, 2, 3], async (value) => { if (value === 3) return Break; return value % 2 === 0; }); expect(result).toStrictEqual([2]); }); it("should save value and break on encountering Last", async () => { const result = await asyncFilter([1, 2, 3], async (value) => { if (value === 3) return Last(value * 10); return value % 2 === 0; }); expect(result).toStrictEqual([2, 30]); }); }); /* ───────────────────────── Set ───────────────────────── */ describe("set", () => { it("should filter values", async () => { const result = await asyncFilter(new Set([1, 2, 3]), async (value) => value % 2 === 0); expect(result).toStrictEqual(new Set([2])); }); it("should break on encountering Break", async () => { const result = await asyncFilter(new Set([1, 2, 3]), async (value) => { if (value === 3) return Break; return value % 2 === 0; }); expect(result).toStrictEqual(new Set([2])); }); it("should save value and break on encountering Last", async () => { const result = await asyncFilter(new Set([1, 2, 3]), async (value) => { if (value === 3) return Last(value * 10); return value % 2 === 0; }); expect(result).toStrictEqual(new Set([2, 30])); }); }); /* ───────────────────────── Map ───────────────────────── */ describe("map", () => { const inputMap = new Map([ [1, 1], [2, 2], [3, 3], ]); it("should filter values", async () => { const result = await asyncFilter(inputMap, async (value) => value % 2 === 0); expect(result).toStrictEqual(new Map([[2, 2]])); }); it("should break on encountering Break", async () => { const result = await asyncFilter(inputMap, async (value) => { if (value === 3) return Break; return value % 2 === 0; }); expect(result).toStrictEqual(new Map([[2, 2]])); }); it("should save value and break on encountering Last", async () => { const result = await asyncFilter(inputMap, async (value) => { if (value === 3) return Last(value * 10); return value % 2 === 0; }); expect(result).toStrictEqual( new Map([ [2, 2], [3, 30], ]), ); }); }); /* ─────────────────────── TypedArray ─────────────────────── */ describe("typedArray", () => { it("should filter values", async () => { const uint8 = new Uint8Array(3); uint8[0] = 1; uint8[1] = 2; uint8[2] = 3; const result = await asyncFilter(uint8, async (value) => value % 2 === 0); const expectedUint8 = new Uint8Array(1); expectedUint8[0] = 2; expect(result).toStrictEqual(expectedUint8); }); it("should break on encountering Break", async () => { const uint8 = new Uint8Array(3); uint8[0] = 1; uint8[1] = 2; uint8[2] = 3; const result = await asyncFilter(uint8, async (value) => { if (value === 3) return Break; return value % 2 === 0; }); const expectedUint8 = new Uint8Array(1); expectedUint8[0] = 2; expect(result).toStrictEqual(expectedUint8); }); it("should save value and break on encountering Last", async () => { const uint8 = new Uint8Array(3); uint8[0] = 1; uint8[1] = 2; uint8[2] = 3; const result = await asyncFilter(uint8, async (value) => { if (value === 3) return Last(value * 10); return value % 2 === 0; }); const expectedUint8 = new Uint8Array(2); expectedUint8[0] = 2; expectedUint8[1] = 30; expect(result).toStrictEqual(expectedUint8); }); }); /* ───────────────────── Plain Object ───────────────────── */ describe("plain object", () => { const input = { "1": 1, "2": 2, "3": 3 }; it("should filter values", async () => { const result = await asyncFilter(input, async (value) => value % 2 === 0); expect(result).toStrictEqual({ "2": 2 }); }); it("should break on encountering Break", async () => { const result = await asyncFilter(input, async (value) => { if (value === 3) return Break; return value % 2 === 0; }); expect(result).toStrictEqual({ "2": 2 }); }); it("should save value and break on encountering Last", async () => { const result = await asyncFilter(input, async (value) => { if (value === 3) return Last(value * 10); return value % 2 === 0; }); expect(result).toStrictEqual({ "2": 2, "3": 30 }); }); }); /* ───────────────────────── Iterator ───────────────────────── */ describe("iterator", () => { it("should filter values", async () => { const iterator = new Set([1, 2, 3]).values(); const result = await asyncFilter(iterator, async (value) => value % 2 === 0); const resultArray: number[] = []; for await (const value of result) { resultArray.push(value as number); } expect(resultArray).toStrictEqual([2]); }); it("should break on encountering Break", async () => { const iterator = new Set([1, 2, 3]).values(); const result = await asyncFilter(iterator, async (value) => { if (value === 3) return Break; return value % 2 === 0; }); const resultArray: number[] = []; for await (const value of result) { resultArray.push(value as number); } expect(resultArray).toStrictEqual([2]); }); it("should save value and break on encountering Last", async () => { const iterator = new Set([1, 2, 3]).values(); const result = await asyncFilter(iterator, async (value) => { if (value === 3) return Last(value * 10); return value % 2 === 0; }); const resultArray: number[] = []; for await (const value of result) { resultArray.push(value as number); } expect(resultArray).toStrictEqual([2, 30]); }); }); /* ─────────────────────── AsyncIterator ─────────────────────── */ describe("asyncIterator", () => { it("should filter values", async () => { const asyncIterator = (async function* (): AsyncGenerator<number, void, void> { yield 1; yield 2; yield 3; })(); const result = await asyncFilter(asyncIterator, async (value) => value % 2 === 0); const resultArray: number[] = []; for await (const value of result) { resultArray.push(value as number); } expect(resultArray).toStrictEqual([2]); }); it("should break on encountering Break", async () => { const asyncIterator = (async function* (): AsyncGenerator<number, void, void> { yield 1; yield 2; yield 3; })(); const result = await asyncFilter(asyncIterator, async (value) => { if (value === 3) return Break; return value % 2 === 0; }); const resultArray: number[] = []; for await (const value of result) { resultArray.push(value as number); } expect(resultArray).toStrictEqual([2]); }); it("should save value and break on encountering Last", async () => { const asyncIterator = (async function* (): AsyncGenerator<number, void, void> { yield 1; yield 2; yield 3; })(); const result = await asyncFilter(asyncIterator, async (value) => { if (value === 3) return Last(value * 10); return value % 2 === 0; }); const resultArray: number[] = []; for await (const value of result) { resultArray.push(value as number); } expect(resultArray).toStrictEqual([2, 30]); }); }); }); >>>>>>> REPLACE ```` > Tokens: 11k sent, 6.9k received. Cost: $0.08 message, $0.08 session. > src/asyncFilter.test.ts > Create new file? (Y)es/(N)o [Yes]: y > Applied edit to src/asyncFilter.test.ts > > > ^C again to exit > > > ^C KeyboardInterrupt