@byloth/core 2.0.0-rc.9 → 2.0.1

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 +4087 -621
  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 +17 -13
  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 +920 -21
  10. package/src/models/aggregators/aggregated-iterator.ts +838 -22
  11. package/src/models/aggregators/reduced-iterator.ts +827 -11
  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 +160 -4
  16. package/src/models/callbacks/switchable-callback.ts +230 -23
  17. package/src/models/callbacks/types.ts +16 -0
  18. package/src/models/exceptions/core.ts +132 -3
  19. package/src/models/exceptions/index.ts +405 -13
  20. package/src/models/index.ts +4 -8
  21. package/src/models/iterators/smart-async-iterator.ts +827 -22
  22. package/src/models/iterators/smart-iterator.ts +755 -20
  23. package/src/models/iterators/types.ts +268 -9
  24. package/src/models/json/json-storage.ts +508 -110
  25. package/src/models/json/types.ts +10 -1
  26. package/src/models/promises/deferred-promise.ts +85 -5
  27. package/src/models/promises/index.ts +1 -3
  28. package/src/models/promises/smart-promise.ts +272 -4
  29. package/src/models/promises/timed-promise.ts +43 -1
  30. package/src/models/promises/types.ts +84 -2
  31. package/src/models/timers/clock.ts +109 -19
  32. package/src/models/timers/countdown.ts +176 -21
  33. package/src/models/timers/game-loop.ts +266 -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 +85 -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 +139 -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,182 @@ 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
+ * ---
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * const iterator = new SmartAsyncIterator<string>(["A", "B", "C"]);
59
+ * ```
60
+ *
61
+ * ---
62
+ *
63
+ * @param iterable The iterable object to wrap.
64
+ */
23
65
  public constructor(iterable: Iterable<T>);
66
+
67
+ /**
68
+ * Initializes a new instance of the {@link SmartAsyncIterator} class.
69
+ *
70
+ * ---
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * const iterator = new SmartAsyncIterator<number>([1, 2, 3, 4, 5]);
75
+ * ```
76
+ *
77
+ * ---
78
+ *
79
+ * @param iterable The asynchronous iterable object to wrap.
80
+ */
24
81
  public constructor(iterable: AsyncIterable<T>);
82
+
83
+ /**
84
+ * Initializes a new instance of the {@link SmartAsyncIterator} class.
85
+ *
86
+ * ---
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * const iterator = new SmartAsyncIterator<number, void, number>({
91
+ * _sum: 0, _count: 0,
92
+ *
93
+ * next: function(value: number)
94
+ * {
95
+ * this._sum += value;
96
+ * this._count += 1;
97
+ *
98
+ * return { done: false, value: this._sum / this._count };
99
+ * }
100
+ * })
101
+ * ```
102
+ *
103
+ * ---
104
+ *
105
+ * @param iterator The iterator object to wrap.
106
+ */
25
107
  public constructor(iterator: Iterator<T, R, N>);
108
+
109
+ /**
110
+ * Initializes a new instance of the {@link SmartAsyncIterator} class.
111
+ *
112
+ * ---
113
+ *
114
+ * @example
115
+ * ```ts
116
+ * const iterator = new SmartAsyncIterator<number, void, number>({
117
+ * _sum: 0, _count: 0,
118
+ *
119
+ * next: async function(value: number)
120
+ * {
121
+ * this._sum += value;
122
+ * this._count += 1;
123
+ *
124
+ * return { done: false, value: this._sum / this._count };
125
+ * }
126
+ * })
127
+ * ```
128
+ *
129
+ * ---
130
+ *
131
+ * @param iterator The asynchronous iterator object to wrap.
132
+ */
26
133
  public constructor(iterator: AsyncIterator<T, R, N>);
134
+
135
+ /**
136
+ * Initializes a new instance of the {@link SmartAsyncIterator} class.
137
+ *
138
+ * ---
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * const iterator = new SmartAsyncIterator<number>(function* ()
143
+ * {
144
+ * for (let i = 2; i < 65_536; i *= 2) { yield (i - 1); }
145
+ * });
146
+ * ```
147
+ *
148
+ * ---
149
+ *
150
+ * @param generatorFn The generator function to wrap.
151
+ */
27
152
  public constructor(generatorFn: GeneratorFunction<T, R, N>);
153
+
154
+ /**
155
+ * Initializes a new instance of the {@link SmartAsyncIterator} class.
156
+ *
157
+ * ---
158
+ *
159
+ * @example
160
+ * ```ts
161
+ * const iterator = new SmartAsyncIterator<number>(async function* ()
162
+ * {
163
+ * for await (let i = 2; i < 65_536; i *= 2) { yield (i - 1); }
164
+ * });
165
+ * ```
166
+ *
167
+ * ---
168
+ *
169
+ * @param generatorFn The asynchronous generator function to wrap.
170
+ */
28
171
  public constructor(generatorFn: AsyncGeneratorFunction<T, R, N>);
172
+
173
+ /**
174
+ * Initializes a new instance of the {@link SmartAsyncIterator} class.
175
+ *
176
+ * ---
177
+ *
178
+ * @example
179
+ * ```ts
180
+ * const iterator = new SmartAsyncIterator(values);
181
+ * ```
182
+ *
183
+ * ---
184
+ *
185
+ * @param argument The synchronous or asynchronous iterable, iterator or generator function to wrap.
186
+ */
29
187
  public constructor(argument: MaybeAsyncIteratorLike<T, R, N> | MaybeAsyncGeneratorFunction<T, R, N>);
30
188
  public constructor(argument: MaybeAsyncIteratorLike<T, R, N> | MaybeAsyncGeneratorFunction<T, R, N>)
31
189
  {
@@ -88,11 +246,38 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
88
246
 
89
247
  })();
90
248
  }
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
249
  }
95
250
 
251
+ /**
252
+ * Determines whether all elements of the iterator satisfy a given condition.
253
+ * See also {@link SmartAsyncIterator.some}.
254
+ *
255
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
256
+ * Once a single element doesn't satisfy the condition, the method will return `false` immediately.
257
+ *
258
+ * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed.
259
+ * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore.
260
+ * Consider using {@link SmartAsyncIterator.find} instead.
261
+ *
262
+ * If the iterator is infinite and every element satisfies the condition, the method will never return.
263
+ *
264
+ * ---
265
+ *
266
+ * @example
267
+ * ```ts
268
+ * const iterator = new SmartAsyncIterator<number>([-2, -1, 0, 1, 2]);
269
+ * const result = await iterator.every(async (value) => value < 0);
270
+ *
271
+ * console.log(result); // false
272
+ * ```
273
+ *
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 all elements satisfy the condition, `false` otherwise.
280
+ */
96
281
  public async every(predicate: MaybeAsyncIteratee<T, boolean>): Promise<boolean>
97
282
  {
98
283
  let index = 0;
@@ -107,6 +292,37 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
107
292
  index += 1;
108
293
  }
109
294
  }
295
+
296
+ /**
297
+ * Determines whether any element of the iterator satisfies a given condition.
298
+ * See also {@link SmartAsyncIterator.every}.
299
+ *
300
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
301
+ * Once a single element satisfies the condition, the method will return `true` immediately.
302
+ *
303
+ * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed.
304
+ * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore.
305
+ * Consider using {@link SmartAsyncIterator.find} instead.
306
+ *
307
+ * If the iterator is infinite and no element satisfies the condition, the method will never return.
308
+ *
309
+ * ---
310
+ *
311
+ * @example
312
+ * ```ts
313
+ * const iterator = new SmartAsyncIterator<number>([-2, -1, 0, 1, 2]);
314
+ * const result = await iterator.some(async (value) => value > 0);
315
+ *
316
+ * console.log(result); // true
317
+ * ```
318
+ *
319
+ * ---
320
+ *
321
+ * @param predicate The condition to check for each element of the iterator.
322
+ *
323
+ * @returns
324
+ * A {@link Promise} that will resolve to `true` if any element satisfies the condition, `false` otherwise.
325
+ */
110
326
  public async some(predicate: MaybeAsyncIteratee<T, boolean>): Promise<boolean>
111
327
  {
112
328
  let index = 0;
@@ -122,8 +338,73 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
122
338
  }
123
339
  }
124
340
 
341
+ /**
342
+ * Filters the elements of the iterator using a given condition.
343
+ *
344
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
345
+ * If the condition is met, the element will be included in the new iterator.
346
+ *
347
+ * Since the iterator is lazy, the filtering process will
348
+ * be executed once the resulting iterator is materialized.
349
+ *
350
+ * A new iterator will be created, holding the reference to the original one.
351
+ * This means that the original iterator won't be consumed until the
352
+ * new one is and that consuming one of them will consume the other as well.
353
+ *
354
+ * ---
355
+ *
356
+ * @example
357
+ * ```ts
358
+ * const iterator = new SmartAsyncIterator<number>([-2, -1, 0, 1, 2]);
359
+ * const result = iterator.filter(async (value) => value < 0);
360
+ *
361
+ * console.log(await result.toArray()); // [-2, -1]
362
+ * ```
363
+ *
364
+ * ---
365
+ *
366
+ * @param predicate The condition to check for each element of the iterator.
367
+ *
368
+ * @returns A new {@link SmartAsyncIterator} containing only the elements that satisfy the condition.
369
+ */
125
370
  public filter(predicate: MaybeAsyncIteratee<T, boolean>): SmartAsyncIterator<T, R>;
126
- public filter<S extends T>(predicate: MaybeAsyncTypeGuardIteratee<T, S>): SmartAsyncIterator<S, R>;
371
+
372
+ /**
373
+ * Filters the elements of the iterator using a given condition.
374
+ *
375
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
376
+ * If the condition is met, the element will be included in the new iterator.
377
+ *
378
+ * Since the iterator is lazy, the filtering process will
379
+ * be executed once the resulting iterator is materialized.
380
+ *
381
+ * A new iterator will be created, holding the reference to the original one.
382
+ * This means that the original iterator won't be consumed until the
383
+ * new one is and that consuming one of them will consume the other as well.
384
+ *
385
+ * ---
386
+ *
387
+ * @example
388
+ * ```ts
389
+ * const iterator = new SmartAsyncIterator<number | string>([-2, "-1", "0", 1, "2"]);
390
+ * const result = iterator.filter<number>(async (value) => typeof value === "number");
391
+ *
392
+ * console.log(await result.toArray()); // [-2, 1]
393
+ * ```
394
+ *
395
+ * ---
396
+ *
397
+ * @template S
398
+ * The type of the elements that satisfy the condition.
399
+ * This allows the type-system to infer the correct type of the new iterator.
400
+ *
401
+ * It must be a subtype of the original type of the elements.
402
+ *
403
+ * @param predicate The type guard condition to check for each element of the iterator.
404
+ *
405
+ * @returns A new {@link SmartAsyncIterator} containing only the elements that satisfy the condition.
406
+ */
407
+ public filter<S extends T>(predicate: MaybeAsyncIteratee<T, boolean>): SmartAsyncIterator<S, R>;
127
408
  public filter(predicate: MaybeAsyncIteratee<T, boolean>): SmartAsyncIterator<T, R>
128
409
  {
129
410
  const iterator = this._iterator;
@@ -131,11 +412,9 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
131
412
  return new SmartAsyncIterator<T, R>(async function* ()
132
413
  {
133
414
  let index = 0;
134
-
135
415
  while (true)
136
416
  {
137
417
  const result = await iterator.next();
138
-
139
418
  if (result.done) { return result.value; }
140
419
  if (await predicate(result.value, index)) { yield result.value; }
141
420
 
@@ -143,6 +422,38 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
143
422
  }
144
423
  });
145
424
  }
425
+
426
+ /**
427
+ * Maps the elements of the iterator using a given transformation function.
428
+ *
429
+ * This method will iterate over all elements of the iterator applying the transformation function.
430
+ * The result of each transformation will be included in the new iterator.
431
+ *
432
+ * Since the iterator is lazy, the mapping process will
433
+ * be executed once the resulting iterator is materialized.
434
+ *
435
+ * A new iterator will be created, holding the reference to the original one.
436
+ * This means that the original iterator won't be consumed until the
437
+ * new one is and that consuming one of them will consume the other as well.
438
+ *
439
+ * ---
440
+ *
441
+ * @example
442
+ * ```ts
443
+ * const iterator = new SmartAsyncIterator<number>([-2, -1, 0, 1, 2]);
444
+ * const result = iterator.map(async (value) => Math.abs(value));
445
+ *
446
+ * console.log(await result.toArray()); // [2, 1, 0, 1, 2]
447
+ * ```
448
+ *
449
+ * ---
450
+ *
451
+ * @template V The type of the elements after the transformation.
452
+ *
453
+ * @param iteratee The transformation function to apply to each element of the iterator.
454
+ *
455
+ * @returns A new {@link SmartAsyncIterator} containing the transformed elements.
456
+ */
146
457
  public map<V>(iteratee: MaybeAsyncIteratee<T, V>): SmartAsyncIterator<V, R>
147
458
  {
148
459
  const iterator = this._iterator;
@@ -150,7 +461,6 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
150
461
  return new SmartAsyncIterator<V, R>(async function* ()
151
462
  {
152
463
  let index = 0;
153
-
154
464
  while (true)
155
465
  {
156
466
  const result = await iterator.next();
@@ -162,7 +472,70 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
162
472
  }
163
473
  });
164
474
  }
475
+
476
+ /**
477
+ * Reduces the elements of the iterator using a given reducer function.
478
+ * This method will consume the entire iterator in the process.
479
+ *
480
+ * It will iterate over all elements of the iterator applying the reducer function.
481
+ * The result of each iteration will be passed as the accumulator to the next one.
482
+ *
483
+ * The first accumulator value will be the first element of the iterator.
484
+ * The last accumulator value will be the final result of the reduction.
485
+ *
486
+ * Also note that:
487
+ * - If an empty iterator is provided, a {@link ValueException} will be thrown.
488
+ * - If the iterator is infinite, the method will never return.
489
+ *
490
+ * ---
491
+ *
492
+ * @example
493
+ * ```ts
494
+ * const iterator = new SmartAsyncIterator<number>([1, 2, 3, 4, 5]);
495
+ * const result = await iterator.reduce(async (acc, value) => acc + value);
496
+ *
497
+ * console.log(result); // 15
498
+ * ```
499
+ *
500
+ * ---
501
+ *
502
+ * @param reducer The reducer function to apply to each element of the iterator.
503
+ *
504
+ * @returns A {@link Promise} that will resolve to the final result of the reduction.
505
+ */
165
506
  public async reduce(reducer: MaybeAsyncReducer<T, T>): Promise<T>;
507
+
508
+ /**
509
+ * Reduces the elements of the iterator using a given reducer function.
510
+ * This method will consume the entire iterator in the process.
511
+ *
512
+ * It will iterate over all elements of the iterator applying the reducer function.
513
+ * The result of each iteration will be passed as the accumulator to the next one.
514
+ *
515
+ * The first accumulator value will be the provided initial value.
516
+ * The last accumulator value will be the final result of the reduction.
517
+ *
518
+ * If the iterator is infinite, the method will never return.
519
+ *
520
+ * ---
521
+ *
522
+ * @example
523
+ * ```ts
524
+ * const iterator = new SmartAsyncIterator<number>([1, 2, 3, 4, 5]);
525
+ * const result = await iterator.reduce(async (acc, value) => acc + value, 10);
526
+ *
527
+ * console.log(result); // 25
528
+ * ```
529
+ *
530
+ * ---
531
+ *
532
+ * @template A The type of the accumulator value which will also be the type of the final result of the reduction.
533
+ *
534
+ * @param reducer The reducer function to apply to each element of the iterator.
535
+ * @param initialValue The initial value of the accumulator.
536
+ *
537
+ * @returns A {@link Promise} that will resolve to the final result of the reduction.
538
+ */
166
539
  public async reduce<A>(reducer: MaybeAsyncReducer<T, A>, initialValue: A): Promise<A>;
167
540
  public async reduce<A>(reducer: MaybeAsyncReducer<T, A>, initialValue?: A): Promise<A>
168
541
  {
@@ -188,31 +561,92 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
188
561
  }
189
562
  }
190
563
 
191
- public flatMap<V>(iteratee: MaybeAsyncIteratee<T, MaybeAsyncIterable<V>>): SmartAsyncIterator<V, R>
564
+ /**
565
+ * Flattens the elements of the iterator using a given transformation function.
566
+ *
567
+ * This method will iterate over all elements of the iterator applying the transformation function.
568
+ * The result of each transformation will be flattened and included in the new iterator.
569
+ *
570
+ * Since the iterator is lazy, the flattening process will
571
+ * be executed once the resulting iterator is materialized.
572
+ *
573
+ * A new iterator will be created, holding the reference to the original one.
574
+ * This means that the original iterator won't be consumed until the
575
+ * new one is and that consuming one of them will consume the other as well.
576
+ *
577
+ * ---
578
+ *
579
+ * @example
580
+ * ```ts
581
+ * const iterator = new SmartAsyncIterator<number[]>([[-2, -1], 0, 1, 2, [3, 4, 5]]);
582
+ * const result = iterator.flatMap(async (value) => value);
583
+ *
584
+ * console.log(await result.toArray()); // [-2, -1, 0, 1, 2, 3, 4, 5]
585
+ * ```
586
+ *
587
+ * ---
588
+ *
589
+ * @template V The type of the elements after the transformation.
590
+ *
591
+ * @param iteratee The transformation function to apply to each element of the iterator.
592
+ *
593
+ * @returns A new {@link SmartAsyncIterator} containing the flattened elements.
594
+ */
595
+ public flatMap<V>(iteratee: MaybeAsyncIteratee<T, V | readonly V[]>): SmartAsyncIterator<V, R>
192
596
  {
193
597
  const iterator = this._iterator;
194
598
 
195
599
  return new SmartAsyncIterator<V, R>(async function* ()
196
600
  {
197
601
  let index = 0;
198
-
199
602
  while (true)
200
603
  {
201
604
  const result = await iterator.next();
202
605
  if (result.done) { return result.value; }
203
606
 
204
607
  const elements = await iteratee(result.value, index);
205
-
206
- for await (const element of elements)
608
+ if (elements instanceof Array)
207
609
  {
208
- yield element;
610
+ for (const value of elements) { yield value; }
209
611
  }
612
+ else { yield elements; }
210
613
 
211
614
  index += 1;
212
615
  }
213
616
  });
214
617
  }
215
618
 
619
+ /**
620
+ * Drops a given number of elements at the beginning of the iterator.
621
+ * The remaining elements will be included in a new iterator.
622
+ * See also {@link SmartAsyncIterator.take}.
623
+ *
624
+ * Since the iterator is lazy, the dropping process will
625
+ * be executed once the resulting iterator is materialized.
626
+ *
627
+ * A new iterator will be created, holding the reference to the original one.
628
+ * This means that the original iterator won't be consumed until the
629
+ * new one is and that consuming one of them will consume the other as well.
630
+ *
631
+ * Only the dropped elements will be consumed in the process.
632
+ * The rest of the iterator will be consumed only once the new one is.
633
+ *
634
+ * ---
635
+ *
636
+ * @example
637
+ * ```ts
638
+ * const iterator = new SmartAsyncIterator<number>([-2, -1, 0, 1, 2]);
639
+ * const result = iterator.drop(3);
640
+ *
641
+ * console.log(await result.toArray()); // [1, 2]
642
+ * ```
643
+ *
644
+ * ---
645
+ *
646
+ * @param count The number of elements to drop.
647
+ *
648
+ * @returns A new {@link SmartAsyncIterator} containing the remaining elements.
649
+ */
216
650
  public drop(count: number): SmartAsyncIterator<T, R | undefined>
217
651
  {
218
652
  const iterator = this._iterator;
@@ -220,7 +654,6 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
220
654
  return new SmartAsyncIterator<T, R | undefined>(async function* ()
221
655
  {
222
656
  let index = 0;
223
-
224
657
  while (index < count)
225
658
  {
226
659
  const result = await iterator.next();
@@ -238,6 +671,39 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
238
671
  }
239
672
  });
240
673
  }
674
+
675
+ /**
676
+ * Takes a given number of elements at the beginning of the iterator.
677
+ * These elements will be included in a new iterator.
678
+ * See also {@link SmartAsyncIterator.drop}.
679
+ *
680
+ * Since the iterator is lazy, the taking process will
681
+ * be executed once the resulting iterator is materialized.
682
+ *
683
+ * A new iterator will be created, holding the reference to the original one.
684
+ * This means that the original iterator won't be consumed until the
685
+ * new one is and that consuming one of them will consume the other as well.
686
+ *
687
+ * Only the taken elements will be consumed from the original iterator.
688
+ * The rest of the original iterator will be available for further consumption.
689
+ *
690
+ * ---
691
+ *
692
+ * @example
693
+ * ```ts
694
+ * const iterator = new SmartAsyncIterator<number>([-2, -1, 0, 1, 2]);
695
+ * const result = iterator.take(3);
696
+ *
697
+ * console.log(await result.toArray()); // [-2, -1, 0]
698
+ * console.log(await iterator.toArray()); // [1, 2]
699
+ * ```
700
+ *
701
+ * ---
702
+ *
703
+ * @param limit The number of elements to take.
704
+ *
705
+ * @returns A new {@link SmartAsyncIterator} containing the taken elements.
706
+ */
241
707
  public take(limit: number): SmartAsyncIterator<T, R | undefined>
242
708
  {
243
709
  const iterator = this._iterator;
@@ -245,7 +711,6 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
245
711
  return new SmartAsyncIterator<T, R | undefined>(async function* ()
246
712
  {
247
713
  let index = 0;
248
-
249
714
  while (index < limit)
250
715
  {
251
716
  const result = await iterator.next();
@@ -260,6 +725,77 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
260
725
  });
261
726
  }
262
727
 
728
+ /**
729
+ * Finds the first element of the iterator that satisfies a given condition.
730
+ *
731
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
732
+ * The first element that satisfies the condition will be returned immediately.
733
+ *
734
+ * Only the elements that are necessary to find the first
735
+ * satisfying one will be consumed from the original iterator.
736
+ * The rest of the original iterator will be available for further consumption.
737
+ *
738
+ * Also note that:
739
+ * - If no element satisfies the condition, `undefined` will be returned once the entire iterator is consumed.
740
+ * - If the iterator is infinite and no element satisfies the condition, the method will never return.
741
+ *
742
+ * ---
743
+ *
744
+ * @example
745
+ * ```ts
746
+ * const iterator = new SmartAsyncIterator<number>([-2, -1, 0, 1, 2]);
747
+ * const result = await iterator.find(async (value) => value > 0);
748
+ *
749
+ * console.log(result); // 1
750
+ * ```
751
+ *
752
+ * ---
753
+ *
754
+ * @param predicate The condition to check for each element of the iterator.
755
+ *
756
+ * @returns
757
+ * A {@link Promise} that will resolve to the first element that satisfies the condition, `undefined` otherwise.
758
+ */
759
+ public async find(predicate: MaybeAsyncIteratee<T, boolean>): Promise<T | undefined>;
760
+
761
+ /**
762
+ * Finds the first element of the iterator that satisfies a given condition.
763
+ *
764
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
765
+ * The first element that satisfies the condition will be returned immediately.
766
+ *
767
+ * Only the elements that are necessary to find the first
768
+ * satisfying one will be consumed from the original iterator.
769
+ * The rest of the original iterator will be available for further consumption.
770
+ *
771
+ * Also note that:
772
+ * - If no element satisfies the condition, `undefined` will be returned once the entire iterator is consumed.
773
+ * - If the iterator is infinite and no element satisfies the condition, the method will never return.
774
+ *
775
+ * ---
776
+ *
777
+ * @example
778
+ * ```ts
779
+ * const iterator = new SmartAsyncIterator<number | string>([-2, "-1", "0", 1, "2"]);
780
+ * const result = await iterator.find<number>(async (value) => typeof value === "number");
781
+ *
782
+ * console.log(result); // -2
783
+ * ```
784
+ *
785
+ * ---
786
+ *
787
+ * @template S
788
+ * The type of the element that satisfies the condition.
789
+ * This allows the type-system to infer the correct type of the result.
790
+ *
791
+ * It must be a subtype of the original type of the elements.
792
+ *
793
+ * @param predicate The type guard condition to check for each element of the iterator.
794
+ *
795
+ * @returns
796
+ * A {@link Promise} that will resolve to the first element that satisfies the condition, `undefined` otherwise.
797
+ */
798
+ public async find<S extends T>(predicate: MaybeAsyncIteratee<T, boolean>): Promise<S | undefined>;
263
799
  public async find(predicate: MaybeAsyncIteratee<T, boolean>): Promise<T | undefined>
264
800
  {
265
801
  let index = 0;
@@ -275,10 +811,64 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
275
811
  }
276
812
  }
277
813
 
814
+ /**
815
+ * Enumerates the elements of the iterator.
816
+ * Each element is be paired with its index in a new iterator.
817
+ *
818
+ * Since the iterator is lazy, the enumeration process will
819
+ * be executed once the resulting iterator is materialized.
820
+ *
821
+ * A new iterator will be created, holding the reference to the original one.
822
+ * This means that the original iterator won't be consumed until the
823
+ * new one is and that consuming one of them will consume the other as well.
824
+ *
825
+ * ---
826
+ *
827
+ * @example
828
+ * ```ts
829
+ * const iterator = new SmartAsyncIterator<string>(["A", "M", "N", "Z"]);
830
+ * const result = iterator.enumerate();
831
+ *
832
+ * for await (const [index, value] of result)
833
+ * {
834
+ * console.log(`${index}: ${value}`); // "0: A", "1: M", "2: N", "3: Z"
835
+ * }
836
+ * ```
837
+ *
838
+ * ---
839
+ *
840
+ * @returns A new {@link SmartAsyncIterator} containing the enumerated elements.
841
+ */
278
842
  public enumerate(): SmartAsyncIterator<[number, T], R>
279
843
  {
280
844
  return this.map((value, index) => [index, value]);
281
845
  }
846
+
847
+ /**
848
+ * Removes all duplicate elements from the iterator.
849
+ * The first occurrence of each element will be kept.
850
+ *
851
+ * Since the iterator is lazy, the deduplication process will
852
+ * be executed once the resulting iterator is materialized.
853
+ *
854
+ * A new iterator will be created, holding the reference to the original one.
855
+ * This means that the original iterator won't be consumed until the
856
+ * new one is and that consuming one of them will consume the other as well.
857
+ *
858
+ * ---
859
+ *
860
+ * @example
861
+ * ```ts
862
+ * const iterator = new SmartAsyncIterator<number>([1, 1, 2, 3, 2, 3, 4, 5, 5, 4]);
863
+ * const result = iterator.unique();
864
+ *
865
+ * console.log(await result.toArray()); // [1, 2, 3, 4, 5]
866
+ * ```
867
+ *
868
+ * ---
869
+ *
870
+ * @returns A new {@link SmartAsyncIterator} containing only the unique elements.
871
+ */
282
872
  public unique(): SmartAsyncIterator<T, R>
283
873
  {
284
874
  const iterator = this._iterator;
@@ -286,11 +876,9 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
286
876
  return new SmartAsyncIterator<T, R>(async function* ()
287
877
  {
288
878
  const values = new Set<T>();
289
-
290
879
  while (true)
291
880
  {
292
881
  const result = await iterator.next();
293
-
294
882
  if (result.done) { return result.value; }
295
883
  if (values.has(result.value)) { continue; }
296
884
 
@@ -301,6 +889,26 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
301
889
  });
302
890
  }
303
891
 
892
+ /**
893
+ * Counts the number of elements in the iterator.
894
+ * This method will consume the entire iterator in the process.
895
+ *
896
+ * If the iterator is infinite, the method will never return.
897
+ *
898
+ * ---
899
+ *
900
+ * @example
901
+ * ```ts
902
+ * const iterator = new SmartAsyncIterator<number>([1, 2, 3, 4, 5]);
903
+ * const result = await iterator.count();
904
+ *
905
+ * console.log(result); // 5
906
+ * ```
907
+ *
908
+ * ---
909
+ *
910
+ * @returns A {@link Promise} that will resolve to the number of elements in the iterator.
911
+ */
304
912
  public async count(): Promise<number>
305
913
  {
306
914
  let index = 0;
@@ -313,6 +921,31 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
313
921
  index += 1;
314
922
  }
315
923
  }
924
+
925
+ /**
926
+ * Iterates over all elements of the iterator.
927
+ * The elements are passed to the function along with their index.
928
+ *
929
+ * This method will consume the entire iterator in the process.
930
+ * If the iterator is infinite, the method will never return.
931
+ *
932
+ * ---
933
+ *
934
+ * @example
935
+ * ```ts
936
+ * const iterator = new SmartAsyncIterator<number>(["A", "M", "N", "Z"]);
937
+ * await iterator.forEach(async (value, index) =>
938
+ * {
939
+ * console.log(`${index}: ${value}`); // "0: A", "1: M", "2: N", "3: Z"
940
+ * }
941
+ * ```
942
+ *
943
+ * ---
944
+ *
945
+ * @param iteratee The function to apply to each element of the iterator.
946
+ *
947
+ * @returns A {@link Promise} that will resolve once the iteration is complete.
948
+ */
316
949
  public async forEach(iteratee: MaybeAsyncIteratee<T>): Promise<void>
317
950
  {
318
951
  let index = 0;
@@ -328,11 +961,160 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
328
961
  }
329
962
  }
330
963
 
964
+ /**
965
+ * Advances the iterator to the next element and returns the result.
966
+ * If the iterator requires it, a value must be provided to be passed to the next element.
967
+ *
968
+ * Once the iterator is done, the method will return an object with the `done` property set to `true`.
969
+ *
970
+ * ---
971
+ *
972
+ * @example
973
+ * ```ts
974
+ * const iterator = new SmartAsyncIterator<number>([1, 2, 3, 4, 5]);
975
+ *
976
+ * let result = await iterator.next();
977
+ * while (!result.done)
978
+ * {
979
+ * console.log(result.value); // 1, 2, 3, 4, 5
980
+ *
981
+ * result = await iterator.next();
982
+ * }
983
+ *
984
+ * console.log(result); // { done: true, value: undefined }
985
+ * ```
986
+ *
987
+ * ---
988
+ *
989
+ * @param values The value to pass to the next element, if required.
990
+ *
991
+ * @returns
992
+ * A {@link Promise} that will resolve to the result of the iteration, containing the value of the operation.
993
+ */
331
994
  public next(...values: N extends undefined ? [] : [N]): Promise<IteratorResult<T, R>>
332
995
  {
333
996
  return this._iterator.next(...values);
334
997
  }
335
998
 
999
+ /**
1000
+ * An utility method that may be used to close the iterator gracefully,
1001
+ * free the resources and perform any cleanup operation.
1002
+ * It may also be used to signal the end or to compute a specific final result of the iteration process.
1003
+ *
1004
+ * ---
1005
+ *
1006
+ * @example
1007
+ * ```ts
1008
+ * const iterator = new SmartAsyncIterator<number>({
1009
+ * _index: 0,
1010
+ * next: async function()
1011
+ * {
1012
+ * return { done: false, value: this._index += 1 };
1013
+ * },
1014
+ * return: async function() { console.log("Closing the iterator..."); }
1015
+ * });
1016
+ *
1017
+ * for await (const value of iterator)
1018
+ * {
1019
+ * if (value > 5) { break; } // Closing the iterator...
1020
+ *
1021
+ * console.log(value); // 1, 2, 3, 4, 5
1022
+ * }
1023
+ * ```
1024
+ *
1025
+ * ---
1026
+ *
1027
+ * @param value The final value of the iterator.
1028
+ *
1029
+ * @returns A {@link Promise} that will resolve to the final result of the iterator.
1030
+ */
1031
+ public async return(value?: MaybePromise<R>): Promise<IteratorResult<T, R>>
1032
+ {
1033
+ const _value = (await value) as R;
1034
+
1035
+ if (this._iterator.return) { return await this._iterator.return(_value); }
1036
+
1037
+ return { done: true, value: _value };
1038
+ }
1039
+
1040
+ /**
1041
+ * An utility method that may be used to close the iterator due to an error,
1042
+ * free the resources and perform any cleanup operation.
1043
+ * It may also be used to signal that an error occurred during the iteration process or to handle it.
1044
+ *
1045
+ * ---
1046
+ *
1047
+ * @example
1048
+ * ```ts
1049
+ * const iterator = new SmartAsyncIterator<number>({
1050
+ * _index: 0,
1051
+ * next: async function()
1052
+ * {
1053
+ * return { done: this._index > 10, value: this._index += 1 };
1054
+ * },
1055
+ * throw: async function(error)
1056
+ * {
1057
+ * console.warn(error.message);
1058
+ *
1059
+ * this._index = 0;
1060
+ * }
1061
+ * });
1062
+ *
1063
+ * for await (const value of iterator) // 1, 2, 3, 4, 5, "The index is too high.", 1, 2, 3, 4, 5, ...
1064
+ * {
1065
+ * try
1066
+ * {
1067
+ * if (value > 5) { throw new Error("The index is too high."); }
1068
+ *
1069
+ * console.log(value); // 1, 2, 3, 4, 5
1070
+ * }
1071
+ * catch (error) { await iterator.throw(error); }
1072
+ * }
1073
+ * ```
1074
+ *
1075
+ * ---
1076
+ *
1077
+ * @param error The error to throw into the iterator.
1078
+ *
1079
+ * @returns A {@link Promise} that will resolve to the final result of the iterator.
1080
+ */
1081
+ public throw(error: unknown): Promise<IteratorResult<T, R>>
1082
+ {
1083
+ if (this._iterator.throw) { return this._iterator.throw(error); }
1084
+
1085
+ throw error;
1086
+ }
1087
+
1088
+ /**
1089
+ * An utility method that aggregates the elements of the iterator using a given key function.
1090
+ * The elements will be grouped by the resulting keys in a new specialized iterator.
1091
+ * See {@link AggregatedAsyncIterator}.
1092
+ *
1093
+ * Since the iterator is lazy, the grouping process will
1094
+ * be executed once the resulting iterator is materialized.
1095
+ *
1096
+ * A new iterator will be created, holding the reference to the original one.
1097
+ * This means that the original iterator won't be consumed until the
1098
+ * the new one is and that consuming one of them will consume the other as well.
1099
+ *
1100
+ * ---
1101
+ *
1102
+ * @example
1103
+ * ```ts
1104
+ * const iterator = new SmartAsyncIterator<number>([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
1105
+ * const result = iterator.groupBy<string>(async (value) => value % 2 === 0 ? "even" : "odd");
1106
+ *
1107
+ * console.log(await result.toObject()); // { odd: [1, 3, 5, 7, 9], even: [2, 4, 6, 8, 10] }
1108
+ * ```
1109
+ *
1110
+ * ---
1111
+ *
1112
+ * @template K The type of the keys used to group the elements.
1113
+ *
1114
+ * @param iteratee The key function to apply to each element of the iterator.
1115
+ *
1116
+ * @returns A new instance of the {@link AggregatedAsyncIterator} class containing the grouped elements.
1117
+ */
336
1118
  public groupBy<K extends PropertyKey>(iteratee: MaybeAsyncIteratee<T, K>): AggregatedAsyncIterator<K, T>
337
1119
  {
338
1120
  return new AggregatedAsyncIterator(this.map(async (element, index) =>
@@ -343,6 +1125,29 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
343
1125
  }));
344
1126
  }
345
1127
 
1128
+ /**
1129
+ * Materializes the iterator into an array.
1130
+ * This method will consume the entire iterator in the process.
1131
+ *
1132
+ * If the iterator is infinite, the method will never return.
1133
+ *
1134
+ * ---
1135
+ *
1136
+ * @example
1137
+ * ```ts
1138
+ * const iterator = new SmartAsyncIterator(async function* ()
1139
+ * {
1140
+ * for (let i = 0; i < 5; i += 1) { yield i; }
1141
+ * });
1142
+ * const result = await iterator.toArray();
1143
+ *
1144
+ * console.log(result); // [0, 1, 2, 3, 4]
1145
+ * ```
1146
+ *
1147
+ * ---
1148
+ *
1149
+ * @returns A {@link Promise} that will resolve to an array containing all elements of the iterator.
1150
+ */
346
1151
  public toArray(): Promise<T[]>
347
1152
  {
348
1153
  return Array.fromAsync(this as AsyncIterable<T>);