extra-iterator 0.11.1 → 0.13.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 CHANGED
@@ -3,6 +3,33 @@ interface ArrayIsh<T> {
3
3
  length: number;
4
4
  }
5
5
  export type ExtraIteratorSource<T> = Iterator<T, any, any> | Iterable<T, any, any> | ArrayIsh<T>;
6
+ /**
7
+ * An extended iterator class that provides additional chainable utility methods for working with iterables.
8
+ *
9
+ * @template T The type of values yielded by this iterator.
10
+ *
11
+ * @example
12
+ * // Creating an iterator from an array
13
+ * const iter = ExtraIterator.from([1, 2, 3, 4, 5]);
14
+ *
15
+ * @example
16
+ * // Creating a sequence of ancestors of a given element
17
+ * ExtraIterator.from(function*() {
18
+ *
19
+ * }()).toArray();
20
+ * ExtraIterator.from([1, 2, 3, 4, 5])
21
+ * .filter(n => n % 2 === 0)
22
+ * .map(n => n * 2)
23
+ * .toArray()
24
+ * // returns [4, 8]
25
+ *
26
+ * @example
27
+ * // Using static factory methods
28
+ * ExtraIterator.range(1, 5)
29
+ * .map(n => n * n)
30
+ * .toArray()
31
+ * // returns [1, 4, 9, 16]
32
+ */
6
33
  export declare class ExtraIterator<T> extends Iterator<T, any, any> {
7
34
  static from<T>(source: ExtraIteratorSource<T>): ExtraIterator<T>;
8
35
  static zip<A, B>(a: ExtraIteratorSource<A>, b: ExtraIteratorSource<B>): ExtraIterator<[A, B]>;
@@ -25,6 +52,12 @@ export declare class ExtraIterator<T> extends Iterator<T, any, any> {
25
52
  * @example ExtraIterator.empty().toArray() // returns []
26
53
  */
27
54
  static empty<T = any>(): ExtraIterator<T>;
55
+ /**
56
+ * Creates an iterator that yields a single value.
57
+ *
58
+ * @example ExtraIterator.single(42).toArray() // returns [42]
59
+ */
60
+ static single<T>(value: T): ExtraIterator<T>;
28
61
  /**
29
62
  * Creates an iterator that yields incrementing numbers.
30
63
  *
@@ -70,14 +103,30 @@ export declare class ExtraIterator<T> extends Iterator<T, any, any> {
70
103
  */
71
104
  static repeat<T>(value: T): ExtraIterator<T>;
72
105
  /**
73
- * Generates an infinite sequence of cryptographically strong random bytes using `crypto.getRandomValues`. Each
74
- * yielded value is a number in between 0 and 255 (inclusive).
106
+ * Generates an infinite sequence of random numbers between 0 and 1 (exclusive) using `Math.random` or another
107
+ * specified random number generator.
75
108
  *
76
109
  * > ⚠ This iterator is infinite. Use {@link take} method if you want a specific number of values.
77
110
  */
78
- static random({ bufferSize }?: {
79
- bufferSize?: number | undefined;
80
- }): ExtraIterator<number>;
111
+ static random(rng?: () => number): ExtraIterator<number>;
112
+ /**
113
+ * Generates an infinite sequence of cryptographically strong random bytes using `crypto.getRandomValues`, in
114
+ * chunks of `chunkSize` bytes.
115
+ *
116
+ * By default, this method reuses the same `ArrayBuffer` instance for each chunk, refilling it with new random
117
+ * values at each iteration, so you should not keep references to the yielded buffers. Set `reuseBuffer` to `false`
118
+ * if you want this method to yield copies of the buffer instead.
119
+ *
120
+ * If you want a flat sequence of individual byte values instead of chunks, you can chain the iterator returned by
121
+ * this method with the {@link flatten} method. The resulting iterator will contain interger values from 0 to 255
122
+ * (inclusive).
123
+ *
124
+ * > ⚠ This iterator is infinite. Use {@link take} method if you want a specific number of values.
125
+ */
126
+ static randomBytes({ chunkSize, reuseBuffer }?: {
127
+ chunkSize?: number | undefined;
128
+ reuseBuffer?: boolean | undefined;
129
+ }): ExtraIterator<ArrayBuffer>;
81
130
  private constructor();
82
131
  private source;
83
132
  next(value?: any): IteratorResult<T, any>;
package/dist/index.js CHANGED
@@ -1,8 +1,34 @@
1
+ /**
2
+ * An extended iterator class that provides additional chainable utility methods for working with iterables.
3
+ *
4
+ * @template T The type of values yielded by this iterator.
5
+ *
6
+ * @example
7
+ * // Creating an iterator from an array
8
+ * const iter = ExtraIterator.from([1, 2, 3, 4, 5]);
9
+ *
10
+ * @example
11
+ * // Creating a sequence of ancestors of a given element
12
+ * ExtraIterator.from(function*() {
13
+ *
14
+ * }()).toArray();
15
+ * ExtraIterator.from([1, 2, 3, 4, 5])
16
+ * .filter(n => n % 2 === 0)
17
+ * .map(n => n * 2)
18
+ * .toArray()
19
+ * // returns [4, 8]
20
+ *
21
+ * @example
22
+ * // Using static factory methods
23
+ * ExtraIterator.range(1, 5)
24
+ * .map(n => n * n)
25
+ * .toArray()
26
+ * // returns [1, 4, 9, 16]
27
+ */
1
28
  export class ExtraIterator extends Iterator {
2
29
  // =================================================================================================================
3
30
  // STATIC FUNCTIONS
4
31
  // =================================================================================================================
5
- // TODO Consider using a lib like `make-iterator` to transform things into iterators
6
32
  static from(source) {
7
33
  if (!(Symbol.iterator in source) && 'length' in source) {
8
34
  return new ExtraIterator(function* () {
@@ -29,6 +55,14 @@ export class ExtraIterator extends Iterator {
29
55
  static empty() {
30
56
  return new ExtraIterator([]);
31
57
  }
58
+ /**
59
+ * Creates an iterator that yields a single value.
60
+ *
61
+ * @example ExtraIterator.single(42).toArray() // returns [42]
62
+ */
63
+ static single(value) {
64
+ return new ExtraIterator([value]);
65
+ }
32
66
  /**
33
67
  * Creates an iterator that yields incrementing numbers.
34
68
  *
@@ -98,16 +132,37 @@ export class ExtraIterator extends Iterator {
98
132
  }());
99
133
  }
100
134
  /**
101
- * Generates an infinite sequence of cryptographically strong random bytes using `crypto.getRandomValues`. Each
102
- * yielded value is a number in between 0 and 255 (inclusive).
135
+ * Generates an infinite sequence of random numbers between 0 and 1 (exclusive) using `Math.random` or another
136
+ * specified random number generator.
103
137
  *
104
138
  * > ⚠ This iterator is infinite. Use {@link take} method if you want a specific number of values.
105
139
  */
106
- static random({ bufferSize = 1024 } = {}) {
107
- const buffer = new Uint8Array(bufferSize);
140
+ static random(rng = Math.random) {
141
+ return ExtraIterator.from(function* () {
142
+ while (true) {
143
+ yield rng();
144
+ }
145
+ }());
146
+ }
147
+ /**
148
+ * Generates an infinite sequence of cryptographically strong random bytes using `crypto.getRandomValues`, in
149
+ * chunks of `chunkSize` bytes.
150
+ *
151
+ * By default, this method reuses the same `ArrayBuffer` instance for each chunk, refilling it with new random
152
+ * values at each iteration, so you should not keep references to the yielded buffers. Set `reuseBuffer` to `false`
153
+ * if you want this method to yield copies of the buffer instead.
154
+ *
155
+ * If you want a flat sequence of individual byte values instead of chunks, you can chain the iterator returned by
156
+ * this method with the {@link flatten} method. The resulting iterator will contain interger values from 0 to 255
157
+ * (inclusive).
158
+ *
159
+ * > ⚠ This iterator is infinite. Use {@link take} method if you want a specific number of values.
160
+ */
161
+ static randomBytes({ chunkSize = 1024, reuseBuffer = true } = {}) {
162
+ const bytes = new Uint8Array(chunkSize);
108
163
  return new ExtraIterator(function* () {
109
- globalThis.crypto.getRandomValues(buffer);
110
- yield* new Uint8Array(buffer);
164
+ globalThis.crypto.getRandomValues(bytes);
165
+ yield reuseBuffer ? bytes.buffer : new Uint8Array(bytes).buffer;
111
166
  }())
112
167
  .loop();
113
168
  }
@@ -140,17 +195,19 @@ export class ExtraIterator extends Iterator {
140
195
  take(limit) {
141
196
  return limit >= 0
142
197
  ? ExtraIterator.from(super.take(limit))
143
- : ExtraIterator.from(this.takeLast(-limit));
198
+ : this.takeLast(-limit);
144
199
  }
145
200
  takeLast(count) {
146
- const result = [];
147
- for (let item; item = this.next(), !item.done;) {
148
- result.push(item.value);
149
- if (result.length > count) {
150
- result.shift();
151
- }
201
+ const ringbuffer = new Array(count);
202
+ let index = 0;
203
+ for (let item; item = this.next(), !item.done; index = (index + 1) % count) {
204
+ ringbuffer[index] = item.value;
152
205
  }
153
- return result;
206
+ return ExtraIterator.from(function* () {
207
+ for (let i = index; i < count + index; i++) {
208
+ yield ringbuffer[i % count];
209
+ }
210
+ }());
154
211
  }
155
212
  drop(count) {
156
213
  return count >= 0
@@ -174,7 +231,11 @@ export class ExtraIterator extends Iterator {
174
231
  * @example ExtraIterator.from([[1, 2], [3, 4]]).flatten().toArray() // returns [1, 2, 3, 4]
175
232
  */
176
233
  flatten() {
177
- return this.flatMap(value => Array.isArray(value) ? new ExtraIterator(value).flatten() : [value]);
234
+ return this.flatMap(value => typeof value === 'object'
235
+ && value !== null
236
+ && Symbol.iterator in value
237
+ ? new ExtraIterator(value).flatten()
238
+ : [value]);
178
239
  }
179
240
  /**
180
241
  * Creates a new iterator that yields the values of this iterator, but won't yield any duplicates.
@@ -37,10 +37,15 @@ describe(ExtraIterator.name, () => {
37
37
  const iterator = ExtraIterator.repeat('x').take(3);
38
38
  expect(iterator.toArray()).toEqual(['x', 'x', 'x']);
39
39
  });
40
- it('should yield random values', () => {
40
+ it('should yield random numbers', () => {
41
41
  const values = ExtraIterator.random().take(10000).toArray();
42
42
  expect(values.length).toBe(10000);
43
- expect(values.every(value => typeof value === 'number' && value >= 0 && value < 256)).toBe(true);
43
+ expect(values.every(value => typeof value === 'number' && value >= 0 && value < 1)).toBe(true);
44
+ });
45
+ it('should yield random bytes', () => {
46
+ const values = ExtraIterator.randomBytes({ chunkSize: 1024 }).take(1).map(chunk => new Uint8Array(chunk)).flatten().toArray();
47
+ expect(values.length).toBe(1024);
48
+ expect(values.every(value => typeof value === 'number' && Number.isInteger(value) && value >= 0 && value <= 255)).toBe(true);
44
49
  });
45
50
  it('should filter values based on a predicate', () => {
46
51
  const iterator = ExtraIterator.from([1, 2, 3, 4]).filter(x => x % 2 === 0);
@@ -70,6 +75,10 @@ describe(ExtraIterator.name, () => {
70
75
  const iterator = ExtraIterator.from([1, 2, 3]);
71
76
  expect(iterator.first()).toBe(1);
72
77
  });
78
+ it('should have a single value', () => {
79
+ const iterator = ExtraIterator.single(42);
80
+ expect(iterator.first()).toBe(42);
81
+ });
73
82
  describe(ExtraIterator.prototype.last.name, () => {
74
83
  it('should return the last value if the iterator is not empty', () => {
75
84
  const iterator = ExtraIterator.from([1, 2, 3]);
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "An extension of the Iterator class with additional utility helper functions.",
4
4
  "author": "Leonardo Raele <leonardoraele@gmail.com>",
5
5
  "license": "MIT",
6
- "version": "0.11.1",
6
+ "version": "0.13.0",
7
7
  "type": "module",
8
8
  "exports": {
9
9
  ".": "./dist/index.js",
@@ -13,9 +13,11 @@
13
13
  "dist"
14
14
  ],
15
15
  "scripts": {
16
- "test": "run-s test:types test:unit",
16
+ "test": "run-p test:*",
17
17
  "test:types": "tsc --noEmit",
18
18
  "test:unit": "node --import=tsx --test **/*.test.ts",
19
+ "coverage": "run-s build coverage:test",
20
+ "coverage:test": "node --import=tsx --test --experimental-test-coverage",
19
21
  "build": "tsc",
20
22
  "prebuild": "rimraf dist",
21
23
  "prepack": "run-s test build"