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
Markdown
# 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