@byloth/core 2.0.0-rc.9 → 2.0.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.
Files changed (47) hide show
  1. package/dist/core.js +3371 -608
  2. package/dist/core.js.map +1 -1
  3. package/dist/core.umd.cjs +2 -2
  4. package/dist/core.umd.cjs.map +1 -1
  5. package/package.json +13 -10
  6. package/src/core/types.ts +41 -0
  7. package/src/helpers.ts +11 -2
  8. package/src/index.ts +12 -9
  9. package/src/models/aggregators/aggregated-async-iterator.ts +765 -21
  10. package/src/models/aggregators/aggregated-iterator.ts +698 -22
  11. package/src/models/aggregators/reduced-iterator.ts +699 -10
  12. package/src/models/aggregators/types.ts +153 -10
  13. package/src/models/callbacks/callable-object.ts +42 -6
  14. package/src/models/callbacks/index.ts +2 -2
  15. package/src/models/callbacks/publisher.ts +139 -4
  16. package/src/models/callbacks/switchable-callback.ts +138 -4
  17. package/src/models/callbacks/types.ts +16 -0
  18. package/src/models/exceptions/core.ts +112 -3
  19. package/src/models/exceptions/index.ts +340 -13
  20. package/src/models/index.ts +4 -8
  21. package/src/models/iterators/smart-async-iterator.ts +687 -22
  22. package/src/models/iterators/smart-iterator.ts +631 -21
  23. package/src/models/iterators/types.ts +268 -9
  24. package/src/models/json/json-storage.ts +388 -110
  25. package/src/models/json/types.ts +10 -1
  26. package/src/models/promises/deferred-promise.ts +75 -5
  27. package/src/models/promises/index.ts +1 -3
  28. package/src/models/promises/smart-promise.ts +232 -4
  29. package/src/models/promises/timed-promise.ts +38 -1
  30. package/src/models/promises/types.ts +84 -2
  31. package/src/models/timers/clock.ts +91 -19
  32. package/src/models/timers/countdown.ts +152 -22
  33. package/src/models/timers/game-loop.ts +243 -0
  34. package/src/models/timers/index.ts +2 -1
  35. package/src/models/types.ts +6 -5
  36. package/src/utils/async.ts +43 -0
  37. package/src/utils/curve.ts +75 -0
  38. package/src/utils/date.ts +204 -10
  39. package/src/utils/dom.ts +16 -2
  40. package/src/utils/index.ts +3 -2
  41. package/src/utils/iterator.ts +200 -17
  42. package/src/utils/math.ts +55 -3
  43. package/src/utils/random.ts +109 -2
  44. package/src/utils/string.ts +11 -0
  45. package/src/models/game-loop.ts +0 -83
  46. package/src/models/promises/long-running-task.ts +0 -294
  47. package/src/models/promises/thenable.ts +0 -97
@@ -1,5 +1,6 @@
1
1
  import AggregatedAsyncIterator from "../aggregators/aggregated-async-iterator.js";
2
2
  import { ValueException } from "../exceptions/index.js";
3
+ import type { MaybePromise } from "../types.js";
3
4
 
4
5
  import type {
5
6
  GeneratorFunction,
@@ -7,25 +8,147 @@ import type {
7
8
  MaybeAsyncGeneratorFunction,
8
9
  MaybeAsyncIteratee,
9
10
  MaybeAsyncReducer,
10
- MaybeAsyncIterable,
11
- MaybeAsyncIteratorLike,
12
- MaybeAsyncTypeGuardIteratee
11
+ MaybeAsyncIteratorLike
13
12
 
14
13
  } from "./types.js";
15
14
 
15
+ /**
16
+ * A wrapper class representing an enhanced and instantiable version
17
+ * of the native {@link AsyncIterable} & {@link AsyncIterator} interfaces.
18
+ *
19
+ * It provides a set of utility methods to better manipulate and transform
20
+ * asynchronous iterators in a functional and highly performant way.
21
+ * It takes inspiration from the native {@link Array} methods like
22
+ * {@link Array.map}, {@link Array.filter}, {@link Array.reduce}, etc...
23
+ *
24
+ * The class is lazy, meaning that the transformations are applied
25
+ * only when the resulting iterator is materialized, not before.
26
+ * This allows to chain multiple transformations without
27
+ * the need to iterate over the elements multiple times.
28
+ *
29
+ * ```ts
30
+ * const result = new SmartAsyncIterator<number>(["-5", "-4", "-3", "-2", "-1", "0", "1", "2", "3", "4", "5"])
31
+ * .map((value) => Number(value))
32
+ * .map((value) => value + Math.ceil(Math.abs(value / 2)))
33
+ * .filter((value) => value >= 0)
34
+ * .map((value) => value + 1)
35
+ * .reduce((acc, value) => acc + value);
36
+ *
37
+ * console.log(await result); // 31
38
+ * ```
39
+ *
40
+ * @template T The type of elements in the iterator.
41
+ * @template R The type of the final result of the iterator. Default is `void`.
42
+ * @template N The type of the argument passed to the `next` method. Default is `undefined`.
43
+ */
16
44
  export default class SmartAsyncIterator<T, R = void, N = undefined> implements AsyncIterator<T, R, N>
17
45
  {
46
+ /**
47
+ * The native {@link AsyncIterator} object that is being wrapped by this instance.
48
+ */
18
49
  protected _iterator: AsyncIterator<T, R, N>;
19
50
 
20
- public return?: (value?: R) => Promise<IteratorResult<T, R>>;
21
- public throw?: (error?: unknown) => Promise<IteratorResult<T, R>>;
22
-
51
+ /**
52
+ * Initializes a new instance of the {@link SmartAsyncIterator} class.
53
+ *
54
+ * ```ts
55
+ * const iterator = new SmartAsyncIterator<string>(["A", "B", "C"]);
56
+ * ```
57
+ *
58
+ * @param iterable The iterable object to wrap.
59
+ */
23
60
  public constructor(iterable: Iterable<T>);
61
+
62
+ /**
63
+ * Initializes a new instance of the {@link SmartAsyncIterator} class.
64
+ *
65
+ * ```ts
66
+ * const iterator = new SmartAsyncIterator<number>([1, 2, 3, 4, 5]);
67
+ * ```
68
+ *
69
+ * @param iterable The asynchronous iterable object to wrap.
70
+ */
24
71
  public constructor(iterable: AsyncIterable<T>);
72
+
73
+ /**
74
+ * Initializes a new instance of the {@link SmartAsyncIterator} class.
75
+ *
76
+ * ```ts
77
+ * const iterator = new SmartAsyncIterator<number, void, number>({
78
+ * _sum: 0, _count: 0,
79
+ *
80
+ * next: function(value: number)
81
+ * {
82
+ * this._sum += value;
83
+ * this._count += 1;
84
+ *
85
+ * return { done: false, value: this._sum / this._count };
86
+ * }
87
+ * })
88
+ * ```
89
+ *
90
+ * @param iterator The iterator object to wrap.
91
+ */
25
92
  public constructor(iterator: Iterator<T, R, N>);
93
+
94
+ /**
95
+ * Initializes a new instance of the {@link SmartAsyncIterator} class.
96
+ *
97
+ * ```ts
98
+ * const iterator = new SmartAsyncIterator<number, void, number>({
99
+ * _sum: 0, _count: 0,
100
+ *
101
+ * next: async function(value: number)
102
+ * {
103
+ * this._sum += value;
104
+ * this._count += 1;
105
+ *
106
+ * return { done: false, value: this._sum / this._count };
107
+ * }
108
+ * })
109
+ * ```
110
+ *
111
+ * @param iterator The asynchronous iterator object to wrap.
112
+ */
26
113
  public constructor(iterator: AsyncIterator<T, R, N>);
114
+
115
+ /**
116
+ * Initializes a new instance of the {@link SmartAsyncIterator} class.
117
+ *
118
+ * ```ts
119
+ * const iterator = new SmartAsyncIterator<number>(function* ()
120
+ * {
121
+ * for (let i = 2; i < 65_536; i *= 2) { yield (i - 1); }
122
+ * });
123
+ * ```
124
+ *
125
+ * @param generatorFn The generator function to wrap.
126
+ */
27
127
  public constructor(generatorFn: GeneratorFunction<T, R, N>);
128
+
129
+ /**
130
+ * Initializes a new instance of the {@link SmartAsyncIterator} class.
131
+ *
132
+ * ```ts
133
+ * const iterator = new SmartAsyncIterator<number>(async function* ()
134
+ * {
135
+ * for await (let i = 2; i < 65_536; i *= 2) { yield (i - 1); }
136
+ * });
137
+ * ```
138
+ *
139
+ * @param generatorFn The asynchronous generator function to wrap.
140
+ */
28
141
  public constructor(generatorFn: AsyncGeneratorFunction<T, R, N>);
142
+
143
+ /**
144
+ * Initializes a new instance of the {@link SmartAsyncIterator} class.
145
+ *
146
+ * ```ts
147
+ * const iterator = new SmartAsyncIterator(values);
148
+ * ```
149
+ *
150
+ * @param argument The synchronous or asynchronous iterable, iterator or generator function to wrap.
151
+ */
29
152
  public constructor(argument: MaybeAsyncIteratorLike<T, R, N> | MaybeAsyncGeneratorFunction<T, R, N>);
30
153
  public constructor(argument: MaybeAsyncIteratorLike<T, R, N> | MaybeAsyncGeneratorFunction<T, R, N>)
31
154
  {
@@ -88,11 +211,33 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
88
211
 
89
212
  })();
90
213
  }
91
-
92
- if (this._iterator.return) { this.return = (value?: R) => this._iterator.return!(value); }
93
- if (this._iterator.throw) { this.throw = (error?: unknown) => this._iterator.throw!(error); }
94
214
  }
95
215
 
216
+ /**
217
+ * Determines whether all elements of the iterator satisfy a given condition.
218
+ * See also {@link SmartAsyncIterator.some}.
219
+ *
220
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
221
+ * Once a single element doesn't satisfy the condition, the method will return `false` immediately.
222
+ *
223
+ * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed.
224
+ * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore.
225
+ * Consider using {@link SmartAsyncIterator.find} instead.
226
+ *
227
+ * If the iterator is infinite and every element satisfies the condition, the method will never return.
228
+ *
229
+ * ```ts
230
+ * const iterator = new SmartAsyncIterator<number>([-2, -1, 0, 1, 2]);
231
+ * const result = await iterator.every(async (value) => value < 0);
232
+ *
233
+ * console.log(result); // false
234
+ * ```
235
+ *
236
+ * @param predicate The condition to check for each element of the iterator.
237
+ *
238
+ * @returns
239
+ * A {@link Promise} that will resolve to `true` if all elements satisfy the condition, `false` otherwise.
240
+ */
96
241
  public async every(predicate: MaybeAsyncIteratee<T, boolean>): Promise<boolean>
97
242
  {
98
243
  let index = 0;
@@ -107,6 +252,32 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
107
252
  index += 1;
108
253
  }
109
254
  }
255
+
256
+ /**
257
+ * Determines whether any element of the iterator satisfies a given condition.
258
+ * See also {@link SmartAsyncIterator.every}.
259
+ *
260
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
261
+ * Once a single element satisfies the condition, the method will return `true` immediately.
262
+ *
263
+ * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed.
264
+ * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore.
265
+ * Consider using {@link SmartAsyncIterator.find} instead.
266
+ *
267
+ * If the iterator is infinite and no element satisfies the condition, the method will never return.
268
+ *
269
+ * ```ts
270
+ * const iterator = new SmartAsyncIterator<number>([-2, -1, 0, 1, 2]);
271
+ * const result = await iterator.some(async (value) => value > 0);
272
+ *
273
+ * console.log(result); // true
274
+ * ```
275
+ *
276
+ * @param predicate The condition to check for each element of the iterator.
277
+ *
278
+ * @returns
279
+ * A {@link Promise} that will resolve to `true` if any element satisfies the condition, `false` otherwise.
280
+ */
110
281
  public async some(predicate: MaybeAsyncIteratee<T, boolean>): Promise<boolean>
111
282
  {
112
283
  let index = 0;
@@ -122,8 +293,63 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
122
293
  }
123
294
  }
124
295
 
296
+ /**
297
+ * Filters the elements of the iterator using a given condition.
298
+ *
299
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
300
+ * If the condition is met, the element will be included in the new iterator.
301
+ *
302
+ * Since the iterator is lazy, the filtering process will
303
+ * be executed once the resulting iterator is materialized.
304
+ *
305
+ * A new iterator will be created, holding the reference to the original one.
306
+ * This means that the original iterator won't be consumed until the
307
+ * new one is and that consuming one of them will consume the other as well.
308
+ *
309
+ * ```ts
310
+ * const iterator = new SmartAsyncIterator<number>([-2, -1, 0, 1, 2]);
311
+ * const result = iterator.filter(async (value) => value < 0);
312
+ *
313
+ * console.log(await result.toArray()); // [-2, -1]
314
+ * ```
315
+ *
316
+ * @param predicate The condition to check for each element of the iterator.
317
+ *
318
+ * @returns A new {@link SmartAsyncIterator} containing only the elements that satisfy the condition.
319
+ */
125
320
  public filter(predicate: MaybeAsyncIteratee<T, boolean>): SmartAsyncIterator<T, R>;
126
- public filter<S extends T>(predicate: MaybeAsyncTypeGuardIteratee<T, S>): SmartAsyncIterator<S, R>;
321
+
322
+ /**
323
+ * Filters the elements of the iterator using a given condition.
324
+ *
325
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
326
+ * If the condition is met, the element will be included in the new iterator.
327
+ *
328
+ * Since the iterator is lazy, the filtering process will
329
+ * be executed once the resulting iterator is materialized.
330
+ *
331
+ * A new iterator will be created, holding the reference to the original one.
332
+ * This means that the original iterator won't be consumed until the
333
+ * new one is and that consuming one of them will consume the other as well.
334
+ *
335
+ * ```ts
336
+ * const iterator = new SmartAsyncIterator<number | string>([-2, "-1", "0", 1, "2"]);
337
+ * const result = iterator.filter<number>(async (value) => typeof value === "number");
338
+ *
339
+ * console.log(await result.toArray()); // [-2, 1]
340
+ * ```
341
+ *
342
+ * @template S
343
+ * The type of the elements that satisfy the condition.
344
+ * This allows the type-system to infer the correct type of the new iterator.
345
+ *
346
+ * It must be a subtype of the original type of the elements.
347
+ *
348
+ * @param predicate The type guard condition to check for each element of the iterator.
349
+ *
350
+ * @returns A new {@link SmartAsyncIterator} containing only the elements that satisfy the condition.
351
+ */
352
+ public filter<S extends T>(predicate: MaybeAsyncIteratee<T, boolean>): SmartAsyncIterator<S, R>;
127
353
  public filter(predicate: MaybeAsyncIteratee<T, boolean>): SmartAsyncIterator<T, R>
128
354
  {
129
355
  const iterator = this._iterator;
@@ -131,11 +357,9 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
131
357
  return new SmartAsyncIterator<T, R>(async function* ()
132
358
  {
133
359
  let index = 0;
134
-
135
360
  while (true)
136
361
  {
137
362
  const result = await iterator.next();
138
-
139
363
  if (result.done) { return result.value; }
140
364
  if (await predicate(result.value, index)) { yield result.value; }
141
365
 
@@ -143,6 +367,33 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
143
367
  }
144
368
  });
145
369
  }
370
+
371
+ /**
372
+ * Maps the elements of the iterator using a given transformation function.
373
+ *
374
+ * This method will iterate over all elements of the iterator applying the transformation function.
375
+ * The result of each transformation will be included in the new iterator.
376
+ *
377
+ * Since the iterator is lazy, the mapping process will
378
+ * be executed once the resulting iterator is materialized.
379
+ *
380
+ * A new iterator will be created, holding the reference to the original one.
381
+ * This means that the original iterator won't be consumed until the
382
+ * new one is and that consuming one of them will consume the other as well.
383
+ *
384
+ * ```ts
385
+ * const iterator = new SmartAsyncIterator<number>([-2, -1, 0, 1, 2]);
386
+ * const result = iterator.map(async (value) => Math.abs(value));
387
+ *
388
+ * console.log(await result.toArray()); // [2, 1, 0, 1, 2]
389
+ * ```
390
+ *
391
+ * @template V The type of the elements after the transformation.
392
+ *
393
+ * @param iteratee The transformation function to apply to each element of the iterator.
394
+ *
395
+ * @returns A new {@link SmartAsyncIterator} containing the transformed elements.
396
+ */
146
397
  public map<V>(iteratee: MaybeAsyncIteratee<T, V>): SmartAsyncIterator<V, R>
147
398
  {
148
399
  const iterator = this._iterator;
@@ -150,7 +401,6 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
150
401
  return new SmartAsyncIterator<V, R>(async function* ()
151
402
  {
152
403
  let index = 0;
153
-
154
404
  while (true)
155
405
  {
156
406
  const result = await iterator.next();
@@ -162,7 +412,60 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
162
412
  }
163
413
  });
164
414
  }
415
+
416
+ /**
417
+ * Reduces the elements of the iterator using a given reducer function.
418
+ * This method will consume the entire iterator in the process.
419
+ *
420
+ * It will iterate over all elements of the iterator applying the reducer function.
421
+ * The result of each iteration will be passed as the accumulator to the next one.
422
+ *
423
+ * The first accumulator value will be the first element of the iterator.
424
+ * The last accumulator value will be the final result of the reduction.
425
+ *
426
+ * Also note that:
427
+ * - If an empty iterator is provided, a {@link ValueException} will be thrown.
428
+ * - If the iterator is infinite, the method will never return.
429
+ *
430
+ * ```ts
431
+ * const iterator = new SmartAsyncIterator<number>([1, 2, 3, 4, 5]);
432
+ * const result = await iterator.reduce(async (acc, value) => acc + value);
433
+ *
434
+ * console.log(result); // 15
435
+ * ```
436
+ *
437
+ * @param reducer The reducer function to apply to each element of the iterator.
438
+ *
439
+ * @returns A {@link Promise} that will resolve to the final result of the reduction.
440
+ */
165
441
  public async reduce(reducer: MaybeAsyncReducer<T, T>): Promise<T>;
442
+
443
+ /**
444
+ * Reduces the elements of the iterator using a given reducer function.
445
+ * This method will consume the entire iterator in the process.
446
+ *
447
+ * It will iterate over all elements of the iterator applying the reducer function.
448
+ * The result of each iteration will be passed as the accumulator to the next one.
449
+ *
450
+ * The first accumulator value will be the provided initial value.
451
+ * The last accumulator value will be the final result of the reduction.
452
+ *
453
+ * If the iterator is infinite, the method will never return.
454
+ *
455
+ * ```ts
456
+ * const iterator = new SmartAsyncIterator<number>([1, 2, 3, 4, 5]);
457
+ * const result = await iterator.reduce(async (acc, value) => acc + value, 10);
458
+ *
459
+ * console.log(result); // 25
460
+ * ```
461
+ *
462
+ * @template A The type of the accumulator value which will also be the type of the final result of the reduction.
463
+ *
464
+ * @param reducer The reducer function to apply to each element of the iterator.
465
+ * @param initialValue The initial value of the accumulator.
466
+ *
467
+ * @returns A {@link Promise} that will resolve to the final result of the reduction.
468
+ */
166
469
  public async reduce<A>(reducer: MaybeAsyncReducer<T, A>, initialValue: A): Promise<A>;
167
470
  public async reduce<A>(reducer: MaybeAsyncReducer<T, A>, initialValue?: A): Promise<A>
168
471
  {
@@ -188,31 +491,82 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
188
491
  }
189
492
  }
190
493
 
191
- public flatMap<V>(iteratee: MaybeAsyncIteratee<T, MaybeAsyncIterable<V>>): SmartAsyncIterator<V, R>
494
+ /**
495
+ * Flattens the elements of the iterator using a given transformation function.
496
+ *
497
+ * This method will iterate over all elements of the iterator applying the transformation function.
498
+ * The result of each transformation will be flattened and included in the new iterator.
499
+ *
500
+ * Since the iterator is lazy, the flattening process will
501
+ * be executed once the resulting iterator is materialized.
502
+ *
503
+ * A new iterator will be created, holding the reference to the original one.
504
+ * This means that the original iterator won't be consumed until the
505
+ * new one is and that consuming one of them will consume the other as well.
506
+ *
507
+ * ```ts
508
+ * const iterator = new SmartAsyncIterator<number[]>([[-2, -1], 0, 1, 2, [3, 4, 5]]);
509
+ * const result = iterator.flatMap(async (value) => value);
510
+ *
511
+ * console.log(await result.toArray()); // [-2, -1, 0, 1, 2, 3, 4, 5]
512
+ * ```
513
+ *
514
+ * @template V The type of the elements after the transformation.
515
+ *
516
+ * @param iteratee The transformation function to apply to each element of the iterator.
517
+ *
518
+ * @returns A new {@link SmartAsyncIterator} containing the flattened elements.
519
+ */
520
+ public flatMap<V>(iteratee: MaybeAsyncIteratee<T, V | readonly V[]>): SmartAsyncIterator<V, R>
192
521
  {
193
522
  const iterator = this._iterator;
194
523
 
195
524
  return new SmartAsyncIterator<V, R>(async function* ()
196
525
  {
197
526
  let index = 0;
198
-
199
527
  while (true)
200
528
  {
201
529
  const result = await iterator.next();
202
530
  if (result.done) { return result.value; }
203
531
 
204
532
  const elements = await iteratee(result.value, index);
205
-
206
- for await (const element of elements)
533
+ if (elements instanceof Array)
207
534
  {
208
- yield element;
535
+ for (const value of elements) { yield value; }
209
536
  }
537
+ else { yield elements; }
210
538
 
211
539
  index += 1;
212
540
  }
213
541
  });
214
542
  }
215
543
 
544
+ /**
545
+ * Drops a given number of elements at the beginning of the iterator.
546
+ * The remaining elements will be included in a new iterator.
547
+ * See also {@link SmartAsyncIterator.take}.
548
+ *
549
+ * Since the iterator is lazy, the dropping process will
550
+ * be executed once the resulting iterator is materialized.
551
+ *
552
+ * A new iterator will be created, holding the reference to the original one.
553
+ * This means that the original iterator won't be consumed until the
554
+ * new one is and that consuming one of them will consume the other as well.
555
+ *
556
+ * Only the dropped elements will be consumed in the process.
557
+ * The rest of the iterator will be consumed only once the new one is.
558
+ *
559
+ * ```ts
560
+ * const iterator = new SmartAsyncIterator<number>([-2, -1, 0, 1, 2]);
561
+ * const result = iterator.drop(3);
562
+ *
563
+ * console.log(await result.toArray()); // [1, 2]
564
+ * ```
565
+ *
566
+ * @param count The number of elements to drop.
567
+ *
568
+ * @returns A new {@link SmartAsyncIterator} containing the remaining elements.
569
+ */
216
570
  public drop(count: number): SmartAsyncIterator<T, R | undefined>
217
571
  {
218
572
  const iterator = this._iterator;
@@ -220,7 +574,6 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
220
574
  return new SmartAsyncIterator<T, R | undefined>(async function* ()
221
575
  {
222
576
  let index = 0;
223
-
224
577
  while (index < count)
225
578
  {
226
579
  const result = await iterator.next();
@@ -238,6 +591,34 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
238
591
  }
239
592
  });
240
593
  }
594
+
595
+ /**
596
+ * Takes a given number of elements at the beginning of the iterator.
597
+ * These elements will be included in a new iterator.
598
+ * See also {@link SmartAsyncIterator.drop}.
599
+ *
600
+ * Since the iterator is lazy, the taking process will
601
+ * be executed once the resulting iterator is materialized.
602
+ *
603
+ * A new iterator will be created, holding the reference to the original one.
604
+ * This means that the original iterator won't be consumed until the
605
+ * new one is and that consuming one of them will consume the other as well.
606
+ *
607
+ * Only the taken elements will be consumed from the original iterator.
608
+ * The rest of the original iterator will be available for further consumption.
609
+ *
610
+ * ```ts
611
+ * const iterator = new SmartAsyncIterator<number>([-2, -1, 0, 1, 2]);
612
+ * const result = iterator.take(3);
613
+ *
614
+ * console.log(await result.toArray()); // [-2, -1, 0]
615
+ * console.log(await iterator.toArray()); // [1, 2]
616
+ * ```
617
+ *
618
+ * @param limit The number of elements to take.
619
+ *
620
+ * @returns A new {@link SmartAsyncIterator} containing the taken elements.
621
+ */
241
622
  public take(limit: number): SmartAsyncIterator<T, R | undefined>
242
623
  {
243
624
  const iterator = this._iterator;
@@ -245,7 +626,6 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
245
626
  return new SmartAsyncIterator<T, R | undefined>(async function* ()
246
627
  {
247
628
  let index = 0;
248
-
249
629
  while (index < limit)
250
630
  {
251
631
  const result = await iterator.next();
@@ -260,6 +640,67 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
260
640
  });
261
641
  }
262
642
 
643
+ /**
644
+ * Finds the first element of the iterator that satisfies a given condition.
645
+ *
646
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
647
+ * The first element that satisfies the condition will be returned immediately.
648
+ *
649
+ * Only the elements that are necessary to find the first
650
+ * satisfying one will be consumed from the original iterator.
651
+ * The rest of the original iterator will be available for further consumption.
652
+ *
653
+ * Also note that:
654
+ * - If no element satisfies the condition, `undefined` will be returned once the entire iterator is consumed.
655
+ * - If the iterator is infinite and no element satisfies the condition, the method will never return.
656
+ *
657
+ * ```ts
658
+ * const iterator = new SmartAsyncIterator<number>([-2, -1, 0, 1, 2]);
659
+ * const result = await iterator.find(async (value) => value > 0);
660
+ *
661
+ * console.log(result); // 1
662
+ * ```
663
+ *
664
+ * @param predicate The condition to check for each element of the iterator.
665
+ *
666
+ * @returns
667
+ * A {@link Promise} that will resolve to the first element that satisfies the condition, `undefined` otherwise.
668
+ */
669
+ public async find(predicate: MaybeAsyncIteratee<T, boolean>): Promise<T | undefined>;
670
+
671
+ /**
672
+ * Finds the first element of the iterator that satisfies a given condition.
673
+ *
674
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
675
+ * The first element that satisfies the condition will be returned immediately.
676
+ *
677
+ * Only the elements that are necessary to find the first
678
+ * satisfying one will be consumed from the original iterator.
679
+ * The rest of the original iterator will be available for further consumption.
680
+ *
681
+ * Also note that:
682
+ * - If no element satisfies the condition, `undefined` will be returned once the entire iterator is consumed.
683
+ * - If the iterator is infinite and no element satisfies the condition, the method will never return.
684
+ *
685
+ * ```ts
686
+ * const iterator = new SmartAsyncIterator<number | string>([-2, "-1", "0", 1, "2"]);
687
+ * const result = await iterator.find<number>(async (value) => typeof value === "number");
688
+ *
689
+ * console.log(result); // -2
690
+ * ```
691
+ *
692
+ * @template S
693
+ * The type of the element that satisfies the condition.
694
+ * This allows the type-system to infer the correct type of the result.
695
+ *
696
+ * It must be a subtype of the original type of the elements.
697
+ *
698
+ * @param predicate The type guard condition to check for each element of the iterator.
699
+ *
700
+ * @returns
701
+ * A {@link Promise} that will resolve to the first element that satisfies the condition, `undefined` otherwise.
702
+ */
703
+ public async find<S extends T>(predicate: MaybeAsyncIteratee<T, boolean>): Promise<S | undefined>;
263
704
  public async find(predicate: MaybeAsyncIteratee<T, boolean>): Promise<T | undefined>
264
705
  {
265
706
  let index = 0;
@@ -275,10 +716,54 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
275
716
  }
276
717
  }
277
718
 
719
+ /**
720
+ * Enumerates the elements of the iterator.
721
+ * Each element is be paired with its index in a new iterator.
722
+ *
723
+ * Since the iterator is lazy, the enumeration process will
724
+ * be executed once the resulting iterator is materialized.
725
+ *
726
+ * A new iterator will be created, holding the reference to the original one.
727
+ * This means that the original iterator won't be consumed until the
728
+ * new one is and that consuming one of them will consume the other as well.
729
+ *
730
+ * ```ts
731
+ * const iterator = new SmartAsyncIterator<string>(["A", "M", "N", "Z"]);
732
+ * const result = iterator.enumerate();
733
+ *
734
+ * for await (const [index, value] of result)
735
+ * {
736
+ * console.log(`${index}: ${value}`); // "0: A", "1: M", "2: N", "3: Z"
737
+ * }
738
+ * ```
739
+ *
740
+ * @returns A new {@link SmartAsyncIterator} containing the enumerated elements.
741
+ */
278
742
  public enumerate(): SmartAsyncIterator<[number, T], R>
279
743
  {
280
744
  return this.map((value, index) => [index, value]);
281
745
  }
746
+
747
+ /**
748
+ * Removes all duplicate elements from the iterator.
749
+ * The first occurrence of each element will be kept.
750
+ *
751
+ * Since the iterator is lazy, the deduplication process will
752
+ * be executed once the resulting iterator is materialized.
753
+ *
754
+ * A new iterator will be created, holding the reference to the original one.
755
+ * This means that the original iterator won't be consumed until the
756
+ * new one is and that consuming one of them will consume the other as well.
757
+ *
758
+ * ```ts
759
+ * const iterator = new SmartAsyncIterator<number>([1, 1, 2, 3, 2, 3, 4, 5, 5, 4]);
760
+ * const result = iterator.unique();
761
+ *
762
+ * console.log(await result.toArray()); // [1, 2, 3, 4, 5]
763
+ * ```
764
+ *
765
+ * @returns A new {@link SmartAsyncIterator} containing only the unique elements.
766
+ */
282
767
  public unique(): SmartAsyncIterator<T, R>
283
768
  {
284
769
  const iterator = this._iterator;
@@ -286,11 +771,9 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
286
771
  return new SmartAsyncIterator<T, R>(async function* ()
287
772
  {
288
773
  const values = new Set<T>();
289
-
290
774
  while (true)
291
775
  {
292
776
  const result = await iterator.next();
293
-
294
777
  if (result.done) { return result.value; }
295
778
  if (values.has(result.value)) { continue; }
296
779
 
@@ -301,6 +784,21 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
301
784
  });
302
785
  }
303
786
 
787
+ /**
788
+ * Counts the number of elements in the iterator.
789
+ * This method will consume the entire iterator in the process.
790
+ *
791
+ * If the iterator is infinite, the method will never return.
792
+ *
793
+ * ```ts
794
+ * const iterator = new SmartAsyncIterator<number>([1, 2, 3, 4, 5]);
795
+ * const result = await iterator.count();
796
+ *
797
+ * console.log(result); // 5
798
+ * ```
799
+ *
800
+ * @returns A {@link Promise} that will resolve to the number of elements in the iterator.
801
+ */
304
802
  public async count(): Promise<number>
305
803
  {
306
804
  let index = 0;
@@ -313,6 +811,26 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
313
811
  index += 1;
314
812
  }
315
813
  }
814
+
815
+ /**
816
+ * Iterates over all elements of the iterator.
817
+ * The elements are passed to the function along with their index.
818
+ *
819
+ * This method will consume the entire iterator in the process.
820
+ * If the iterator is infinite, the method will never return.
821
+ *
822
+ * ```ts
823
+ * const iterator = new SmartAsyncIterator<number>(["A", "M", "N", "Z"]);
824
+ * await iterator.forEach(async (value, index) =>
825
+ * {
826
+ * console.log(`${index}: ${value}`); // "0: A", "1: M", "2: N", "3: Z"
827
+ * }
828
+ * ```
829
+ *
830
+ * @param iteratee The function to apply to each element of the iterator.
831
+ *
832
+ * @returns A {@link Promise} that will resolve once the iteration is complete.
833
+ */
316
834
  public async forEach(iteratee: MaybeAsyncIteratee<T>): Promise<void>
317
835
  {
318
836
  let index = 0;
@@ -328,11 +846,140 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
328
846
  }
329
847
  }
330
848
 
849
+ /**
850
+ * Advances the iterator to the next element and returns the result.
851
+ * If the iterator requires it, a value must be provided to be passed to the next element.
852
+ *
853
+ * Once the iterator is done, the method will return an object with the `done` property set to `true`.
854
+ *
855
+ * ```ts
856
+ * const iterator = new SmartAsyncIterator<number>([1, 2, 3, 4, 5]);
857
+ *
858
+ * let result = await iterator.next();
859
+ * while (!result.done)
860
+ * {
861
+ * console.log(result.value); // 1, 2, 3, 4, 5
862
+ *
863
+ * result = await iterator.next();
864
+ * }
865
+ *
866
+ * console.log(result); // { done: true, value: undefined }
867
+ * ```
868
+ *
869
+ * @param values The value to pass to the next element, if required.
870
+ *
871
+ * @returns
872
+ * A {@link Promise} that will resolve to the result of the iteration, containing the value of the operation.
873
+ */
331
874
  public next(...values: N extends undefined ? [] : [N]): Promise<IteratorResult<T, R>>
332
875
  {
333
876
  return this._iterator.next(...values);
334
877
  }
335
878
 
879
+ /**
880
+ * An utility method that may be used to close the iterator gracefully,
881
+ * free the resources and perform any cleanup operation.
882
+ * It may also be used to signal the end or to compute a specific final result of the iteration process.
883
+ *
884
+ * ```ts
885
+ * const iterator = new SmartAsyncIterator<number>({
886
+ * _index: 0,
887
+ * next: async function()
888
+ * {
889
+ * return { done: false, value: this._index += 1 };
890
+ * },
891
+ * return: async function() { console.log("Closing the iterator..."); }
892
+ * });
893
+ *
894
+ * for await (const value of iterator)
895
+ * {
896
+ * if (value > 5) { break; } // Closing the iterator...
897
+ *
898
+ * console.log(value); // 1, 2, 3, 4, 5
899
+ * }
900
+ * ```
901
+ *
902
+ * @param value The final value of the iterator.
903
+ *
904
+ * @returns A {@link Promise} that will resolve to the final result of the iterator.
905
+ */
906
+ public async return(value?: MaybePromise<R>): Promise<IteratorResult<T, R>>
907
+ {
908
+ const _value = (await value) as R;
909
+
910
+ if (this._iterator.return) { return await this._iterator.return(_value); }
911
+
912
+ return { done: true, value: _value };
913
+ }
914
+
915
+ /**
916
+ * An utility method that may be used to close the iterator due to an error,
917
+ * free the resources and perform any cleanup operation.
918
+ * It may also be used to signal that an error occurred during the iteration process or to handle it.
919
+ *
920
+ * ```ts
921
+ * const iterator = new SmartAsyncIterator<number>({
922
+ * _index: 0,
923
+ * next: async function()
924
+ * {
925
+ * return { done: this._index > 10, value: this._index += 1 };
926
+ * },
927
+ * throw: async function(error)
928
+ * {
929
+ * console.warn(error.message);
930
+ *
931
+ * this._index = 0;
932
+ * }
933
+ * });
934
+ *
935
+ * for await (const value of iterator) // 1, 2, 3, 4, 5, "The index is too high.", 1, 2, 3, 4, 5, ...
936
+ * {
937
+ * try
938
+ * {
939
+ * if (value > 5) { throw new Error("The index is too high."); }
940
+ *
941
+ * console.log(value); // 1, 2, 3, 4, 5
942
+ * }
943
+ * catch (error) { await iterator.throw(error); }
944
+ * }
945
+ * ```
946
+ *
947
+ * @param error The error to throw into the iterator.
948
+ *
949
+ * @returns A {@link Promise} that will resolve to the final result of the iterator.
950
+ */
951
+ public throw(error: unknown): Promise<IteratorResult<T, R>>
952
+ {
953
+ if (this._iterator.throw) { return this._iterator.throw(error); }
954
+
955
+ throw error;
956
+ }
957
+
958
+ /**
959
+ * An utility method that aggregates the elements of the iterator using a given key function.
960
+ * The elements will be grouped by the resulting keys in a new specialized iterator.
961
+ * See {@link AggregatedAsyncIterator}.
962
+ *
963
+ * Since the iterator is lazy, the grouping process will
964
+ * be executed once the resulting iterator is materialized.
965
+ *
966
+ * A new iterator will be created, holding the reference to the original one.
967
+ * This means that the original iterator won't be consumed until the
968
+ * the new one is and that consuming one of them will consume the other as well.
969
+ *
970
+ * ```ts
971
+ * const iterator = new SmartAsyncIterator<number>([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
972
+ * const result = iterator.groupBy<string>(async (value) => value % 2 === 0 ? "even" : "odd");
973
+ *
974
+ * console.log(await result.toObject()); // { odd: [1, 3, 5, 7, 9], even: [2, 4, 6, 8, 10] }
975
+ * ```
976
+ *
977
+ * @template K The type of the keys used to group the elements.
978
+ *
979
+ * @param iteratee The key function to apply to each element of the iterator.
980
+ *
981
+ * @returns A new instance of the {@link AggregatedAsyncIterator} class containing the grouped elements.
982
+ */
336
983
  public groupBy<K extends PropertyKey>(iteratee: MaybeAsyncIteratee<T, K>): AggregatedAsyncIterator<K, T>
337
984
  {
338
985
  return new AggregatedAsyncIterator(this.map(async (element, index) =>
@@ -343,6 +990,24 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
343
990
  }));
344
991
  }
345
992
 
993
+ /**
994
+ * Materializes the iterator into an array.
995
+ * This method will consume the entire iterator in the process.
996
+ *
997
+ * If the iterator is infinite, the method will never return.
998
+ *
999
+ * ```ts
1000
+ * const iterator = new SmartAsyncIterator(async function* ()
1001
+ * {
1002
+ * for (let i = 0; i < 5; i += 1) { yield i; }
1003
+ * });
1004
+ * const result = await iterator.toArray();
1005
+ *
1006
+ * console.log(result); // [0, 1, 2, 3, 4]
1007
+ * ```
1008
+ *
1009
+ * @returns A {@link Promise} that will resolve to an array containing all elements of the iterator.
1010
+ */
346
1011
  public toArray(): Promise<T[]>
347
1012
  {
348
1013
  return Array.fromAsync(this as AsyncIterable<T>);