Skip to content

Commit c36483d

Browse files
committed
initial
0 parents  commit c36483d

15 files changed

+1462
-0
lines changed

.github/workflows/ci.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
schedule:
11+
- cron: '0 0 * * 0'
12+
13+
jobs:
14+
check:
15+
runs-on: ${{ matrix.os }}
16+
strategy:
17+
fail-fast: false
18+
matrix:
19+
include:
20+
- os: ubuntu-latest
21+
deno: v1.x
22+
steps:
23+
- uses: actions/checkout@v2
24+
- uses: denoland/setup-deno@v1
25+
with:
26+
deno-version: ${{ matrix.deno }}
27+
- run: deno lint
28+
- run: deno fmt --check
29+
- run: deno test --no-run ./test.ts
30+
test:
31+
needs: [check]
32+
runs-on: ${{ matrix.os }}
33+
strategy:
34+
fail-fast: false
35+
matrix:
36+
include:
37+
- os: ubuntu-latest
38+
deno: v1.x
39+
- os: ubuntu-latest
40+
deno: v1.10
41+
- os: ubuntu-latest
42+
deno: v1.13
43+
- os: ubuntu-latest
44+
deno: canary
45+
- os: windows-latest
46+
deno: v1.x
47+
- os: macos-latest
48+
deno: v1.x
49+
steps:
50+
- uses: actions/checkout@v2
51+
- uses: denoland/setup-deno@v1
52+
with:
53+
deno-version: ${{ matrix.deno }}
54+
- run: make coverage/lcov

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
coverage/

Makefile

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
.PHONY: default
2+
default: help
3+
4+
.PHONY: help
5+
help:
6+
@echo Usage: make <target...>
7+
8+
.PHONY: test
9+
test:
10+
deno test
11+
12+
.PHONY: test/coverage
13+
test/coverage:
14+
deno test --coverage=coverage
15+
16+
.PHONY: coverage/lcov
17+
coverage/lcov: test/coverage
18+
deno coverage coverage --lcov > coverage/coverage.lcov
19+
20+
.PHONY: coverage/html
21+
coverage/html: test/coverage coverage/lcov
22+
genhtml -o coverage/html coverage/coverage.lcov
23+
@echo "open coverage/html/index.html"

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# deno-iterator-helpers
2+
3+
Strict and wrapper version implementation for
4+
https://github.com/tc39/proposal-iterator-helpers.
5+
6+
## Goals
7+
8+
- Implement all proposed features with wrapper API.
9+
10+
## Non-goals
11+
12+
- To make comprehensive library.
13+
- Just include defined features in the proposal.
14+
- To extend global prototype.
15+
- Provide APIs via wrapper and method chaining.
16+
17+
## Iteration
18+
19+
- Until proposal becomes stage4, keeping it v0.x and up to date with bumping
20+
minor if there is breaking change.
21+
- When interfaces are determined, bump major.
22+
- When implemented natively, keep maintaining for months.
23+
- After it passes some months, archive this project.
24+
25+
## Links
26+
27+
- https://github.com/tc39/proposal-iterator-helpers
28+
- https://tc39.es/proposal-iterator-helpers

deps.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * as asserts from "https://deno.land/std@0.106.0/testing/asserts.ts";

lib/async_iterator_from.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// deno-lint-ignore-file no-explicit-any
2+
3+
export type AsyncIteratorLike<T> =
4+
| AsyncIterator<T>
5+
| Iterator<T>
6+
| AsyncIterable<T>
7+
| Iterable<T>;
8+
export const asyncIteratorFrom = <T>(
9+
obj: AsyncIteratorLike<T>,
10+
): AsyncIterator<T> => {
11+
const getAsyncIte = (obj as any)[Symbol.asyncIterator];
12+
if (getAsyncIte != null) {
13+
if (typeof getAsyncIte !== "function") {
14+
throw new TypeError(`${getAsyncIte} is not a function`);
15+
}
16+
const ite: AsyncIterator<T> = getAsyncIte.call(obj);
17+
if (typeof ite !== "object" && typeof ite !== "function") {
18+
throw new TypeError(`[@@asyncIterator]() is non-object`);
19+
}
20+
return ite;
21+
}
22+
const getSyncIte = (obj as any)[Symbol.iterator];
23+
if (getSyncIte != null) {
24+
if (typeof getSyncIte !== "function") {
25+
throw new TypeError(`${getSyncIte} is not a function`);
26+
}
27+
const ite: Iterator<T> = getSyncIte.call(obj);
28+
if (typeof ite !== "object" && typeof ite !== "function") {
29+
throw new TypeError(`[@@iterator]() is non-object`);
30+
}
31+
return (async function* () {
32+
for (const v of { [Symbol.iterator]: () => ite }) yield v;
33+
})();
34+
}
35+
if (typeof obj !== "object" && typeof obj !== "function") {
36+
throw new TypeError(`asyncIteratorFrom called on non-object`);
37+
}
38+
const { next } = obj as any;
39+
if (typeof next !== "function") {
40+
throw new TypeError(`Property next is not a function`);
41+
}
42+
// Proposal needs to return AsyncIterator instance, but currently there is no such a real prototype.
43+
return obj as any;
44+
};

lib/iterator_from.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// deno-lint-ignore-file no-explicit-any
2+
3+
export type IteratorLike<T> = Iterator<T> | Iterable<T>;
4+
export const iteratorFrom = <T>(obj: IteratorLike<T>): Iterator<T> => {
5+
const getSyncIte = (obj as any)[Symbol.iterator];
6+
if (getSyncIte != null) {
7+
if (typeof getSyncIte !== "function") {
8+
throw new TypeError(`${getSyncIte} is not a function`);
9+
}
10+
const ite: Iterator<T> = getSyncIte.call(obj);
11+
if (typeof ite !== "object" && typeof ite !== "function") {
12+
throw new TypeError(`[@@iterator]() is non-object`);
13+
}
14+
return (function* () {
15+
for (const v of { [Symbol.iterator]: () => ite }) yield v;
16+
})();
17+
}
18+
if (typeof obj !== "object" && typeof obj !== "function") {
19+
throw new TypeError(`iteratorFrom called on non-object`);
20+
}
21+
const { next } = obj as any;
22+
if (typeof next !== "function") {
23+
throw new TypeError(`Property next is not a function`);
24+
}
25+
// Proposal needs to return Iterator instance, but currently there is no such a real prototype.
26+
return obj as any;
27+
};

lib/wrap_async_iterator.ts

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// deno-lint-ignore-file no-explicit-any
2+
3+
export type WrappedAsyncIterator<T> = {
4+
unwrap: () => AsyncIterator<T>;
5+
map: <U>(mapperFn: (t: T) => U | Promise<U>) => WrappedAsyncIterator<U>;
6+
filter: {
7+
<U extends T>(
8+
filtererFn: (t: T) => t is U,
9+
): WrappedAsyncIterator<U>;
10+
(
11+
filtererFn: (t: T) => boolean | Promise<boolean>,
12+
): WrappedAsyncIterator<T>;
13+
};
14+
take: (limit: number) => WrappedAsyncIterator<T>;
15+
drop: (limit: number) => WrappedAsyncIterator<T>;
16+
asIndexedPairs: () => WrappedAsyncIterator<[number, T]>;
17+
flatMap: <U>(
18+
mapperFn: (t: T) => U | U[] | Promise<U | U[]>,
19+
) => WrappedAsyncIterator<U>;
20+
reduce: {
21+
<U>(
22+
reducerFn: (u: U, t: T) => U | Promise<U>,
23+
initialValue: U,
24+
): Promise<U>;
25+
<U>(
26+
reducerFn: (u: U | undefined, t: T) => U | Promise<U>,
27+
): Promise<U | undefined>;
28+
};
29+
toArray: () => Promise<T[]>;
30+
forEach: (fn: (t: T) => void | Promise<void>) => Promise<void>;
31+
some: (fn: (t: T) => boolean | Promise<boolean>) => Promise<boolean>;
32+
every: (fn: (t: T) => boolean | Promise<boolean>) => Promise<boolean>;
33+
find: (fn: (t: T) => boolean | Promise<boolean>) => Promise<T | undefined>;
34+
};
35+
36+
export const wrapAsyncIterator = <T>(
37+
ite: AsyncIterator<T>,
38+
): WrappedAsyncIterator<T> => {
39+
return {
40+
unwrap: () => ite,
41+
map: (mapperFn) => {
42+
if (typeof mapperFn !== "function") {
43+
throw new TypeError(`${mapperFn} is not a function`);
44+
}
45+
return wrapAsyncIterator((async function* () {
46+
for await (const v of { [Symbol.asyncIterator]: () => ite }) {
47+
yield await mapperFn(v);
48+
}
49+
})());
50+
},
51+
filter: (filtererFn: any) => {
52+
if (typeof filtererFn !== "function") {
53+
throw new TypeError(`${filtererFn} is not a function`);
54+
}
55+
return wrapAsyncIterator((async function* () {
56+
for await (const v of { [Symbol.asyncIterator]: () => ite }) {
57+
if (await filtererFn(v)) yield v;
58+
}
59+
})());
60+
},
61+
take: (limit) => {
62+
if (typeof limit === "symbol") {
63+
throw new TypeError("Cannot convert a Symbol value to a number");
64+
}
65+
if (typeof limit === "bigint") {
66+
throw new TypeError("Cannot convert a BigInt value to a number");
67+
}
68+
if (limit < 0) throw new RangeError(`Invalid limit value`);
69+
if (Number.isFinite(limit)) limit = Math.floor(limit);
70+
if (Number.isNaN(limit)) limit = 0;
71+
return wrapAsyncIterator((async function* () {
72+
let remaining = limit;
73+
for await (const v of { [Symbol.asyncIterator]: () => ite }) {
74+
if (remaining === 0) break;
75+
if (remaining !== Infinity) remaining -= 1;
76+
yield v;
77+
if (remaining === 0) break;
78+
}
79+
})());
80+
},
81+
drop: (limit) => {
82+
if (typeof limit === "symbol") {
83+
throw new TypeError("Cannot convert a Symbol value to a number");
84+
}
85+
if (typeof limit === "bigint") {
86+
throw new TypeError("Cannot convert a BigInt value to a number");
87+
}
88+
if (limit < 0) throw new RangeError(`Invalid limit value`);
89+
if (Number.isFinite(limit)) limit = Math.floor(limit);
90+
if (Number.isNaN(limit)) limit = 0;
91+
return wrapAsyncIterator((async function* () {
92+
let remaining = limit;
93+
for await (const v of { [Symbol.asyncIterator]: () => ite }) {
94+
if (remaining > 0 && remaining !== Infinity) {
95+
remaining -= 1;
96+
continue;
97+
}
98+
yield v;
99+
}
100+
})());
101+
},
102+
asIndexedPairs: () => {
103+
return wrapAsyncIterator((async function* () {
104+
let i = 0;
105+
for await (const v of { [Symbol.asyncIterator]: () => ite }) {
106+
yield [i, v] as [number, T];
107+
i += 1;
108+
}
109+
})());
110+
},
111+
flatMap: (mapperFn) => {
112+
if (typeof mapperFn !== "function") {
113+
throw new TypeError(`${mapperFn} is not a function`);
114+
}
115+
return wrapAsyncIterator((async function* () {
116+
for await (const v of { [Symbol.asyncIterator]: () => ite }) {
117+
const arr = await mapperFn(v);
118+
if (Array.isArray(arr)) {
119+
for (const e of arr) yield e;
120+
} else {
121+
yield arr;
122+
}
123+
}
124+
})());
125+
},
126+
reduce: (async (reducerFn: any, initialValue: any) => {
127+
if (typeof reducerFn !== "function") {
128+
throw new TypeError(`${reducerFn} is not a function`);
129+
}
130+
let current = initialValue;
131+
for await (const v of { [Symbol.asyncIterator]: () => ite }) {
132+
current = await reducerFn(current, v);
133+
}
134+
return current;
135+
}) as any,
136+
toArray: async () => {
137+
const arr: T[] = [];
138+
for await (const v of { [Symbol.asyncIterator]: () => ite }) {
139+
arr.push(v);
140+
}
141+
return arr;
142+
},
143+
forEach: async (fn) => {
144+
if (typeof fn !== "function") {
145+
throw new TypeError(`${fn} is not a function`);
146+
}
147+
for await (const v of { [Symbol.asyncIterator]: () => ite }) {
148+
await fn(v);
149+
}
150+
},
151+
some: async (fn) => {
152+
if (typeof fn !== "function") {
153+
throw new TypeError(`${fn} is not a function`);
154+
}
155+
for await (const v of { [Symbol.asyncIterator]: () => ite }) {
156+
if (await fn(v)) return true;
157+
}
158+
return false;
159+
},
160+
every: async (fn) => {
161+
if (typeof fn !== "function") {
162+
throw new TypeError(`${fn} is not a function`);
163+
}
164+
for await (const v of { [Symbol.asyncIterator]: () => ite }) {
165+
if (!await fn(v)) return false;
166+
}
167+
return true;
168+
},
169+
find: async (fn) => {
170+
if (typeof fn !== "function") {
171+
throw new TypeError(`${fn} is not a function`);
172+
}
173+
for await (const v of { [Symbol.asyncIterator]: () => ite }) {
174+
if (await fn(v)) return v;
175+
}
176+
},
177+
};
178+
};

0 commit comments

Comments
 (0)