@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,18 +1,99 @@
1
1
  import AggregatedIterator from "../aggregators/aggregated-iterator.js";
2
2
  import { ValueException } from "../exceptions/index.js";
3
3
 
4
- import type { GeneratorFunction, Iteratee, TypeGuardIteratee, Reducer, IteratorLike } from "./types.js";
5
-
4
+ import type { GeneratorFunction, Iteratee, TypeGuardPredicate, Reducer, IteratorLike } from "./types.js";
5
+
6
+ /**
7
+ * A wrapper class representing an enhanced and instantiable version
8
+ * of the native {@link Iterable} & {@link Iterator} interfaces.
9
+ *
10
+ * It provides a set of utility methods to better manipulate and
11
+ * transform iterators in a functional and highly performant way.
12
+ * It takes inspiration from the native {@link Array} methods like
13
+ * {@link Array.map}, {@link Array.filter}, {@link Array.reduce}, etc...
14
+ *
15
+ * The class is lazy, meaning that the transformations are applied
16
+ * only when the resulting iterator is materialized, not before.
17
+ * This allows to chain multiple transformations without
18
+ * the need to iterate over the elements multiple times.
19
+ *
20
+ * ```ts
21
+ * const result = new SmartIterator<number>(["-5", "-4", "-3", "-2", "-1", "0", "1", "2", "3", "4", "5"])
22
+ * .map(Number)
23
+ * .map((value) => value + Math.ceil(Math.abs(value / 2)))
24
+ * .filter((value) => value >= 0)
25
+ * .map((value) => value + 1)
26
+ * .reduce((acc, value) => acc + value);
27
+ *
28
+ * console.log(result); // 31
29
+ * ```
30
+ *
31
+ * @template T The type of elements in the iterator.
32
+ * @template R The type of the final result of the iterator. Default is `void`.
33
+ * @template N The type of the argument required by the `next` method. Default is `undefined`.
34
+ */
6
35
  export default class SmartIterator<T, R = void, N = undefined> implements Iterator<T, R, N>
7
36
  {
37
+ /**
38
+ * The native {@link Iterator} object that is being wrapped by this instance.
39
+ */
8
40
  protected _iterator: Iterator<T, R, N>;
9
41
 
10
- public return?: (value?: R) => IteratorResult<T, R>;
11
- public throw?: (error?: unknown) => IteratorResult<T, R>;
12
-
42
+ /**
43
+ * Initializes a new instance of the {@link SmartIterator} class.
44
+ *
45
+ * ```ts
46
+ * const iterator = new SmartIterator<string>(["A", "B", "C"]);
47
+ * ```
48
+ *
49
+ * @param iterable The iterable object to wrap.
50
+ */
13
51
  public constructor(iterable: Iterable<T, R, N>);
52
+
53
+ /**
54
+ * Initializes a new instance of the {@link SmartIterator} class.
55
+ *
56
+ * ```ts
57
+ * const iterator = new SmartIterator<number, void, number>({
58
+ * _sum: 0, _count: 0,
59
+ *
60
+ * next: function(value: number)
61
+ * {
62
+ * this._sum += value;
63
+ * this._count += 1;
64
+ *
65
+ * return { done: false, value: this._sum / this._count };
66
+ * }
67
+ * })
68
+ * ```
69
+ *
70
+ * @param iterator The iterator object to wrap.
71
+ */
14
72
  public constructor(iterator: Iterator<T, R, N>);
73
+
74
+ /**
75
+ * Initializes a new instance of the {@link SmartIterator} class.
76
+ *
77
+ * ```ts
78
+ * const iterator = new SmartIterator<number>(function* ()
79
+ * {
80
+ * for (let i = 2; i < 65_536; i *= 2) { yield (i - 1); }
81
+ * });
82
+ * ```
83
+ *
84
+ * @param generatorFn The generator function to wrap.
85
+ */
15
86
  public constructor(generatorFn: GeneratorFunction<T, R, N>);
87
+
88
+ /**
89
+ * Initializes a new instance of the {@link SmartIterator} class.
90
+ *
91
+ * ```ts
92
+ * const iterator = new SmartIterator(values);
93
+ * ```
94
+ *
95
+ * @param argument The iterable, iterator or generator function to wrap.
96
+ */
16
97
  public constructor(argument: IteratorLike<T, R, N> | GeneratorFunction<T, R, N>);
17
98
  public constructor(argument: IteratorLike<T, R, N> | GeneratorFunction<T, R, N>)
18
99
  {
@@ -28,11 +109,32 @@ export default class SmartIterator<T, R = void, N = undefined> implements Iterat
28
109
  {
29
110
  this._iterator = argument;
30
111
  }
31
-
32
- if (this._iterator.return) { this.return = (value) => this._iterator.return!(value); }
33
- if (this._iterator.throw) { this.throw = (error) => this._iterator.throw!(error); }
34
112
  }
35
113
 
114
+ /**
115
+ * Determines whether all elements of the iterator satisfy a given condition.
116
+ * See also {@link SmartIterator.some}.
117
+ *
118
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
119
+ * Once a single element doesn't satisfy the condition, the method will return `false` immediately.
120
+ *
121
+ * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed.
122
+ * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore.
123
+ * Consider using {@link SmartIterator.find} instead.
124
+ *
125
+ * If the iterator is infinite and every element satisfies the condition, the method will never return.
126
+ *
127
+ * ```ts
128
+ * const iterator = new SmartIterator<number>([-2, -1, 0, 1, 2]);
129
+ * const result = iterator.every((value) => value < 0);
130
+ *
131
+ * console.log(result); // false
132
+ * ```
133
+ *
134
+ * @param predicate The condition to check for each element of the iterator.
135
+ *
136
+ * @returns `true` if all elements satisfy the condition, `false` otherwise.
137
+ */
36
138
  public every(predicate: Iteratee<T, boolean>): boolean
37
139
  {
38
140
  let index = 0;
@@ -47,6 +149,31 @@ export default class SmartIterator<T, R = void, N = undefined> implements Iterat
47
149
  index += 1;
48
150
  }
49
151
  }
152
+
153
+ /**
154
+ * Determines whether any element of the iterator satisfies a given condition.
155
+ * See also {@link SmartIterator.every}.
156
+ *
157
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
158
+ * Once a single element satisfies the condition, the method will return `true` immediately.
159
+ *
160
+ * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed.
161
+ * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore.
162
+ * Consider using {@link SmartIterator.find} instead.
163
+ *
164
+ * If the iterator is infinite and no element satisfies the condition, the method will never return.
165
+ *
166
+ * ```ts
167
+ * const iterator = new SmartIterator<number>([-2, -1, 0, 1, 2]);
168
+ * const result = iterator.some((value) => value < 0);
169
+ *
170
+ * console.log(result); // true
171
+ * ```
172
+ *
173
+ * @param predicate The condition to check for each element of the iterator.
174
+ *
175
+ * @returns `true` if any element satisfies the condition, `false` otherwise.
176
+ */
50
177
  public some(predicate: Iteratee<T, boolean>): boolean
51
178
  {
52
179
  let index = 0;
@@ -62,8 +189,63 @@ export default class SmartIterator<T, R = void, N = undefined> implements Iterat
62
189
  }
63
190
  }
64
191
 
192
+ /**
193
+ * Filters the elements of the iterator using a given condition.
194
+ *
195
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
196
+ * If the condition is met, the element will be included in the new iterator.
197
+ *
198
+ * Since the iterator is lazy, the filtering process will
199
+ * be executed once the resulting iterator is materialized.
200
+ *
201
+ * A new iterator will be created, holding the reference to the original one.
202
+ * This means that the original iterator won't be consumed until the
203
+ * new one is and that consuming one of them will consume the other as well.
204
+ *
205
+ * ```ts
206
+ * const iterator = new SmartIterator<number>([-2, -1, 0, 1, 2]);
207
+ * const result = iterator.filter((value) => value < 0);
208
+ *
209
+ * console.log(result.toArray()); // [-2, -1]
210
+ * ```
211
+ *
212
+ * @param predicate The condition to check for each element of the iterator.
213
+ *
214
+ * @returns A new {@link SmartIterator} containing only the elements that satisfy the condition.
215
+ */
65
216
  public filter(predicate: Iteratee<T, boolean>): SmartIterator<T, R>;
66
- public filter<S extends T>(predicate: TypeGuardIteratee<T, S>): SmartIterator<S, R>;
217
+
218
+ /**
219
+ * Filters the elements of the iterator using a given condition.
220
+ *
221
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
222
+ * If the condition is met, the element will be included in the new iterator.
223
+ *
224
+ * Since the iterator is lazy, the filtering process will
225
+ * be executed once the resulting iterator is materialized.
226
+ *
227
+ * A new iterator will be created, holding the reference to the original one.
228
+ * This means that the original iterator won't be consumed until the
229
+ * new one is and that consuming one of them will consume the other as well.
230
+ *
231
+ * ```ts
232
+ * const iterator = new SmartIterator<number | string>([-2, "-1", "0", 1, "2"]);
233
+ * const result = iterator.filter<number>((value) => typeof value === "number");
234
+ *
235
+ * console.log(result.toArray()); // [-2, 1]
236
+ * ```
237
+ *
238
+ * @template S
239
+ * The type of the elements that satisfy the condition.
240
+ * This allows the type-system to infer the correct type of the new iterator.
241
+ *
242
+ * It must be a subtype of the original type of the elements.
243
+ *
244
+ * @param predicate The type guard condition to check for each element of the iterator.
245
+ *
246
+ * @returns A new {@link SmartIterator} containing only the elements that satisfy the condition.
247
+ */
248
+ public filter<S extends T>(predicate: TypeGuardPredicate<T, S>): SmartIterator<S, R>;
67
249
  public filter(predicate: Iteratee<T, boolean>): SmartIterator<T, R>
68
250
  {
69
251
  const iterator = this._iterator;
@@ -71,11 +253,9 @@ export default class SmartIterator<T, R = void, N = undefined> implements Iterat
71
253
  return new SmartIterator<T, R>(function* ()
72
254
  {
73
255
  let index = 0;
74
-
75
256
  while (true)
76
257
  {
77
258
  const result = iterator.next();
78
-
79
259
  if (result.done) { return result.value; }
80
260
  if (predicate(result.value, index)) { yield result.value; }
81
261
 
@@ -83,6 +263,33 @@ export default class SmartIterator<T, R = void, N = undefined> implements Iterat
83
263
  }
84
264
  });
85
265
  }
266
+
267
+ /**
268
+ * Maps the elements of the iterator using a given transformation function.
269
+ *
270
+ * This method will iterate over all elements of the iterator applying the transformation function.
271
+ * The result of each transformation will be included in the new iterator.
272
+ *
273
+ * Since the iterator is lazy, the mapping process will
274
+ * be executed once the resulting iterator is materialized.
275
+ *
276
+ * A new iterator will be created, holding the reference to the original one.
277
+ * This means that the original iterator won't be consumed until the
278
+ * new one is and that consuming one of them will consume the other as well.
279
+ *
280
+ * ```ts
281
+ * const iterator = new SmartIterator<number>([-2, -1, 0, 1, 2]);
282
+ * const result = iterator.map((value) => Math.abs(value));
283
+ *
284
+ * console.log(result.toArray()); // [2, 1, 0, 1, 2]
285
+ * ```
286
+ *
287
+ * @template V The type of the elements after the transformation.
288
+ *
289
+ * @param iteratee The transformation function to apply to each element of the iterator.
290
+ *
291
+ * @returns A new {@link SmartIterator} containing the transformed elements.
292
+ */
86
293
  public map<V>(iteratee: Iteratee<T, V>): SmartIterator<V, R>
87
294
  {
88
295
  const iterator = this._iterator;
@@ -90,7 +297,6 @@ export default class SmartIterator<T, R = void, N = undefined> implements Iterat
90
297
  return new SmartIterator<V, R>(function* ()
91
298
  {
92
299
  let index = 0;
93
-
94
300
  while (true)
95
301
  {
96
302
  const result = iterator.next();
@@ -102,7 +308,60 @@ export default class SmartIterator<T, R = void, N = undefined> implements Iterat
102
308
  }
103
309
  });
104
310
  }
311
+
312
+ /**
313
+ * Reduces the elements of the iterator using a given reducer function.
314
+ * This method will consume the entire iterator in the process.
315
+ *
316
+ * It will iterate over all elements of the iterator applying the reducer function.
317
+ * The result of each iteration will be passed as the accumulator to the next one.
318
+ *
319
+ * The first accumulator value will be the first element of the iterator.
320
+ * The last accumulator value will be the final result of the reduction.
321
+ *
322
+ * Also note that:
323
+ * - If an empty iterator is provided, a {@link ValueException} will be thrown.
324
+ * - If the iterator is infinite, the method will never return.
325
+ *
326
+ * ```ts
327
+ * const iterator = new SmartIterator<number>([1, 2, 3, 4, 5]);
328
+ * const result = iterator.reduce((acc, value) => acc + value);
329
+ *
330
+ * console.log(result); // 15
331
+ * ```
332
+ *
333
+ * @param reducer The reducer function to apply to each element of the iterator.
334
+ *
335
+ * @returns The final result of the reduction.
336
+ */
105
337
  public reduce(reducer: Reducer<T, T>): T;
338
+
339
+ /**
340
+ * Reduces the elements of the iterator using a given reducer function.
341
+ * This method will consume the entire iterator in the process.
342
+ *
343
+ * It will iterate over all elements of the iterator applying the reducer function.
344
+ * The result of each iteration will be passed as the accumulator to the next one.
345
+ *
346
+ * The first accumulator value will be the provided initial value.
347
+ * The last accumulator value will be the final result of the reduction.
348
+ *
349
+ * If the iterator is infinite, the method will never return.
350
+ *
351
+ * ```ts
352
+ * const iterator = new SmartIterator<number>([1, 2, 3, 4, 5]);
353
+ * const result = iterator.reduce((acc, value) => acc + value, 10);
354
+ *
355
+ * console.log(result); // 25
356
+ * ```
357
+ *
358
+ * @template A The type of the accumulator value which will also be the type of the final result of the reduction.
359
+ *
360
+ * @param reducer The reducer function to apply to each element of the iterator.
361
+ * @param initialValue The initial value of the accumulator.
362
+ *
363
+ * @returns The final result of the reduction.
364
+ */
106
365
  public reduce<A>(reducer: Reducer<T, A>, initialValue: A): A;
107
366
  public reduce<A>(reducer: Reducer<T, A>, initialValue?: A): A
108
367
  {
@@ -128,30 +387,82 @@ export default class SmartIterator<T, R = void, N = undefined> implements Iterat
128
387
  }
129
388
  }
130
389
 
131
- public flatMap<V>(iteratee: Iteratee<T, Iterable<V>>): SmartIterator<V, R>
390
+ /**
391
+ * Flattens the elements of the iterator using a given transformation function.
392
+ *
393
+ * This method will iterate over all elements of the iterator applying the transformation function.
394
+ * The result of each transformation will be flattened into the new iterator.
395
+ *
396
+ * Since the iterator is lazy, the flattening process will
397
+ * be executed once the resulting iterator is materialized.
398
+ *
399
+ * A new iterator will be created, holding the reference to the original one.
400
+ * This means that the original iterator won't be consumed until the
401
+ * new one is and that consuming one of them will consume the other as well.
402
+ *
403
+ * ```ts
404
+ * const iterator = new SmartIterator<number[]>([[-2, -1], 0, 1, 2, [3, 4, 5]]);
405
+ * const result = iterator.flatMap((value) => value);
406
+ *
407
+ * console.log(result.toArray()); // [-2, -1, 0, 1, 2, 3, 4, 5]
408
+ * ```
409
+ *
410
+ * @template V The type of the elements after the transformation.
411
+ *
412
+ * @param iteratee The transformation function to apply to each element of the iterator.
413
+ *
414
+ * @returns A new {@link SmartIterator} containing the flattened elements.
415
+ */
416
+ public flatMap<V>(iteratee: Iteratee<T, V | readonly V[]>): SmartIterator<V, R>
132
417
  {
133
418
  const iterator = this._iterator;
134
419
 
135
420
  return new SmartIterator<V, R>(function* ()
136
421
  {
137
422
  let index = 0;
138
-
139
423
  while (true)
140
424
  {
141
425
  const result = iterator.next();
142
426
  if (result.done) { return result.value; }
143
427
 
144
- const iterable = iteratee(result.value, index);
145
- for (const value of iterable)
428
+ const elements = iteratee(result.value, index);
429
+ if (elements instanceof Array)
146
430
  {
147
- yield value;
431
+ for (const value of elements) { yield value; }
148
432
  }
433
+ else { yield elements; }
149
434
 
150
435
  index += 1;
151
436
  }
152
437
  });
153
438
  }
154
439
 
440
+ /**
441
+ * Drops a given number of elements at the beginning of the iterator.
442
+ * The remaining elements will be included in a new iterator.
443
+ * See also {@link SmartIterator.take}.
444
+ *
445
+ * Since the iterator is lazy, the dropping process will
446
+ * be executed once the resulting iterator is materialized.
447
+ *
448
+ * A new iterator will be created, holding the reference to the original one.
449
+ * This means that the original iterator won't be consumed until the
450
+ * new one is and that consuming one of them will consume the other as well.
451
+ *
452
+ * Only the dropped elements will be consumed in the process.
453
+ * The rest of the iterator will be consumed only once the new one is.
454
+ *
455
+ * ```ts
456
+ * const iterator = new SmartIterator<number>([-2, -1, 0, 1, 2]);
457
+ * const result = iterator.drop(3);
458
+ *
459
+ * console.log(result.toArray()); // [1, 2]
460
+ * ```
461
+ *
462
+ * @param count The number of elements to drop.
463
+ *
464
+ * @returns A new {@link SmartIterator} containing the remaining elements.
465
+ */
155
466
  public drop(count: number): SmartIterator<T, R | undefined>
156
467
  {
157
468
  const iterator = this._iterator;
@@ -176,6 +487,34 @@ export default class SmartIterator<T, R = void, N = undefined> implements Iterat
176
487
  }
177
488
  });
178
489
  }
490
+
491
+ /**
492
+ * Takes a given number of elements at the beginning of the iterator.
493
+ * These elements will be included in a new iterator.
494
+ * See also {@link SmartIterator.drop}.
495
+ *
496
+ * Since the iterator is lazy, the taking process will
497
+ * be executed once the resulting iterator is materialized.
498
+ *
499
+ * A new iterator will be created, holding the reference to the original one.
500
+ * This means that the original iterator won't be consumed until the
501
+ * new one is and that consuming one of them will consume the other as well.
502
+ *
503
+ * Only the taken elements will be consumed from the original iterator.
504
+ * The rest of the original iterator will be available for further consumption.
505
+ *
506
+ * ```ts
507
+ * const iterator = new SmartIterator<number>([-2, -1, 0, 1, 2]);
508
+ * const result = iterator.take(3);
509
+ *
510
+ * console.log(result.toArray()); // [-2, -1, 0]
511
+ * console.log(iterator.toArray()); // [1, 2]
512
+ * ```
513
+ *
514
+ * @param limit The number of elements to take.
515
+ *
516
+ * @returns A new {@link SmartIterator} containing the taken elements.
517
+ */
179
518
  public take(limit: number): SmartIterator<T, R | undefined>
180
519
  {
181
520
  const iterator = this._iterator;
@@ -197,8 +536,65 @@ export default class SmartIterator<T, R = void, N = undefined> implements Iterat
197
536
  });
198
537
  }
199
538
 
539
+ /**
540
+ * Finds the first element of the iterator that satisfies a given condition.
541
+ *
542
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
543
+ * The first element that satisfies the condition will be returned immediately.
544
+ *
545
+ * Only the elements that are necessary to find the first
546
+ * satisfying one will be consumed from the original iterator.
547
+ * The rest of the original iterator will be available for further consumption.
548
+ *
549
+ * Also note that:
550
+ * - If no element satisfies the condition, `undefined` will be returned once the entire iterator is consumed.
551
+ * - If the iterator is infinite and no element satisfies the condition, the method will never return.
552
+ *
553
+ * ```ts
554
+ * const iterator = new SmartIterator<number>([-2, -1, 0, 1, 2]);
555
+ * const result = iterator.find((value) => value > 0);
556
+ *
557
+ * console.log(result); // 1
558
+ * ```
559
+ *
560
+ * @param predicate The condition to check for each element of the iterator.
561
+ *
562
+ * @returns The first element that satisfies the condition, `undefined` otherwise.
563
+ */
200
564
  public find(predicate: Iteratee<T, boolean>): T | undefined;
201
- public find<S extends T>(predicate: TypeGuardIteratee<T, S>): S | undefined;
565
+
566
+ /**
567
+ * Finds the first element of the iterator that satisfies a given condition.
568
+ *
569
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
570
+ * The first element that satisfies the condition will be returned immediately.
571
+ *
572
+ * Only the elements that are necessary to find the first
573
+ * satisfying one will be consumed from the original iterator.
574
+ * The rest of the original iterator will be available for further consumption.
575
+ *
576
+ * Also note that:
577
+ * - If no element satisfies the condition, `undefined` will be returned once the entire iterator is consumed.
578
+ * - If the iterator is infinite and no element satisfies the condition, the method will never return.
579
+ *
580
+ * ```ts
581
+ * const iterator = new SmartIterator<number | string>([-2, "-1", "0", 1, "2"]);
582
+ * const result = iterator.find<number>((value) => typeof value === "number");
583
+ *
584
+ * console.log(result); // -2
585
+ * ```
586
+ *
587
+ * @template S
588
+ * The type of the element that satisfies the condition.
589
+ * This allows the type-system to infer the correct type of the result.
590
+ *
591
+ * It must be a subtype of the original type of the elements.
592
+ *
593
+ * @param predicate The type guard condition to check for each element of the iterator.
594
+ *
595
+ * @returns The first element that satisfies the condition, `undefined` otherwise.
596
+ */
597
+ public find<S extends T>(predicate: TypeGuardPredicate<T, S>): S | undefined;
202
598
  public find(predicate: Iteratee<T, boolean>): T | undefined
203
599
  {
204
600
  let index = 0;
@@ -214,10 +610,51 @@ export default class SmartIterator<T, R = void, N = undefined> implements Iterat
214
610
  }
215
611
  }
216
612
 
613
+ /**
614
+ * Enumerates the elements of the iterator.
615
+ * Each element is be paired with its index in a new iterator.
616
+ *
617
+ * Since the iterator is lazy, the enumeration process will
618
+ * be executed once the resulting iterator is materialized.
619
+ *
620
+ * A new iterator will be created, holding the reference to the original one.
621
+ * This means that the original iterator won't be consumed until the
622
+ * new one is and that consuming one of them will consume the other as well.
623
+ *
624
+ * ```ts
625
+ * const iterator = new SmartIterator<string>(["A", "M", "N", "Z"]);
626
+ * const result = iterator.enumerate();
627
+ *
628
+ * console.log(result.toArray()); // [[0, "A"], [1, "M"], [2, "N"], [3, "Z"]]
629
+ * ```
630
+ *
631
+ * @returns A new {@link SmartIterator} containing the enumerated elements.
632
+ */
217
633
  public enumerate(): SmartIterator<[number, T], R>
218
634
  {
219
635
  return this.map((value, index) => [index, value]);
220
636
  }
637
+
638
+ /**
639
+ * Removes all duplicate elements from the iterator.
640
+ * The first occurrence of each element will be kept.
641
+ *
642
+ * Since the iterator is lazy, the deduplication process will
643
+ * be executed once the resulting iterator is materialized.
644
+ *
645
+ * A new iterator will be created, holding the reference to the original one.
646
+ * This means that the original iterator won't be consumed until the
647
+ * new one is and that consuming one of them will consume the other as well.
648
+ *
649
+ * ```ts
650
+ * const iterator = new SmartIterator<number>([1, 1, 2, 3, 2, 3, 4, 5, 5, 4]);
651
+ * const result = iterator.unique();
652
+ *
653
+ * console.log(result.toArray()); // [1, 2, 3, 4, 5]
654
+ * ```
655
+ *
656
+ * @returns A new {@link SmartIterator} containing only the unique elements.
657
+ */
221
658
  public unique(): SmartIterator<T, R>
222
659
  {
223
660
  const iterator = this._iterator;
@@ -225,14 +662,11 @@ export default class SmartIterator<T, R = void, N = undefined> implements Iterat
225
662
  return new SmartIterator<T, R>(function* ()
226
663
  {
227
664
  const values = new Set<T>();
228
-
229
665
  while (true)
230
666
  {
231
667
  const result = iterator.next();
232
-
233
668
  if (result.done) { return result.value; }
234
669
  if (values.has(result.value)) { continue; }
235
-
236
670
  values.add(result.value);
237
671
 
238
672
  yield result.value;
@@ -240,6 +674,21 @@ export default class SmartIterator<T, R = void, N = undefined> implements Iterat
240
674
  });
241
675
  }
242
676
 
677
+ /**
678
+ * Counts the number of elements in the iterator.
679
+ * This method will consume the entire iterator in the process.
680
+ *
681
+ * If the iterator is infinite, the method will never return.
682
+ *
683
+ * ```ts
684
+ * const iterator = new SmartIterator<number>([1, 2, 3, 4, 5]);
685
+ * const result = iterator.count();
686
+ *
687
+ * console.log(result); // 5
688
+ * ```
689
+ *
690
+ * @returns The number of elements in the iterator.
691
+ */
243
692
  public count(): number
244
693
  {
245
694
  let index = 0;
@@ -253,6 +702,23 @@ export default class SmartIterator<T, R = void, N = undefined> implements Iterat
253
702
  }
254
703
  }
255
704
 
705
+ /**
706
+ * Iterates over all elements of the iterator.
707
+ * The elements are passed to the function along with their index.
708
+ *
709
+ * This method will consume the entire iterator in the process.
710
+ * If the iterator is infinite, the method will never return.
711
+ *
712
+ * ```ts
713
+ * const iterator = new SmartIterator<number>(["A", "M", "N", "Z"]);
714
+ * iterator.forEach((value, index) =>
715
+ * {
716
+ * console.log(`${index}: ${value}`); // "0: A", "1: M", "2: N", "3: Z"
717
+ * }
718
+ * ```
719
+ *
720
+ * @param iteratee The function to apply to each element of the iterator.
721
+ */
256
722
  public forEach(iteratee: Iteratee<T>): void
257
723
  {
258
724
  let index = 0;
@@ -268,11 +734,137 @@ export default class SmartIterator<T, R = void, N = undefined> implements Iterat
268
734
  }
269
735
  }
270
736
 
737
+ /**
738
+ * Advances the iterator to the next element and returns the result.
739
+ * If the iterator requires it, a value must be provided to be passed to the next element.
740
+ *
741
+ * Once the iterator is done, the method will return an object with the `done` property set to `true`.
742
+ *
743
+ * ```ts
744
+ * const iterator = new SmartIterator<number>([1, 2, 3, 4, 5]);
745
+ *
746
+ * let result = iterator.next();
747
+ * while (!result.done)
748
+ * {
749
+ * console.log(result.value); // 1, 2, 3, 4, 5
750
+ *
751
+ * result = iterator.next();
752
+ * }
753
+ *
754
+ * console.log(result); // { done: true, value: undefined }
755
+ * ```
756
+ *
757
+ * @param values The value to pass to the next element, if required.
758
+ *
759
+ * @returns The result of the iteration, containing the value of the operation.
760
+ */
271
761
  public next(...values: N extends undefined ? [] : [N]): IteratorResult<T, R>
272
762
  {
273
763
  return this._iterator.next(...values);
274
764
  }
275
765
 
766
+ /**
767
+ * An utility method that may be used to close the iterator gracefully,
768
+ * free the resources and perform any cleanup operation.
769
+ * It may also be used to signal the end or to compute a specific final result of the iteration process.
770
+ *
771
+ * ```ts
772
+ * const iterator = new SmartIterator<number>({
773
+ * _index: 0,
774
+ * next: function()
775
+ * {
776
+ * return { done: false, value: this._index += 1 };
777
+ * },
778
+ * return: function() { console.log("Closing the iterator..."); }
779
+ * });
780
+ *
781
+ * for (const value of iterator)
782
+ * {
783
+ * if (value > 5) { break; } // Closing the iterator...
784
+ *
785
+ * console.log(value); // 1, 2, 3, 4, 5
786
+ * }
787
+ * ```
788
+ *
789
+ * @param value The final value of the iterator.
790
+ *
791
+ * @returns The result of the iterator.
792
+ */
793
+ public return(value?: R): IteratorResult<T, R>
794
+ {
795
+ if (this._iterator.return) { return this._iterator.return(value); }
796
+
797
+ return { done: true, value: value as R };
798
+ }
799
+
800
+ /**
801
+ * An utility method that may be used to close the iterator due to an error,
802
+ * free the resources and perform any cleanup operation.
803
+ * It may also be used to signal that an error occurred during the iteration process or to handle it.
804
+ *
805
+ * ```ts
806
+ * const iterator = new SmartIterator<number>({
807
+ * _index: 0,
808
+ * next: function()
809
+ * {
810
+ * return { done: this._index > 10, value: this._index += 1 };
811
+ * },
812
+ * throw: function(error)
813
+ * {
814
+ * console.warn(error.message);
815
+ *
816
+ * this._index = 0;
817
+ * }
818
+ * });
819
+ *
820
+ * for (const value of iterator) // 1, 2, 3, 4, 5, "The index is too high.", 1, 2, 3, 4, 5, ...
821
+ * {
822
+ * try
823
+ * {
824
+ * if (value > 5) { throw new Error("The index is too high."); }
825
+ *
826
+ * console.log(value); // 1, 2, 3, 4, 5
827
+ * }
828
+ * catch (error) { iterator.throw(error); }
829
+ * }
830
+ * ```
831
+ *
832
+ * @param error The error to throw into the iterator.
833
+ *
834
+ * @returns The final result of the iterator.
835
+ */
836
+ public throw(error: unknown): IteratorResult<T, R>
837
+ {
838
+ if (this._iterator.throw) { return this._iterator.throw(error); }
839
+
840
+ throw error;
841
+ }
842
+
843
+ /**
844
+ * An utility method that aggregates the elements of the iterator using a given key function.
845
+ * The elements will be grouped by the resulting keys in a new specialized iterator.
846
+ * See {@link AggregatedIterator}.
847
+ *
848
+ * Since the iterator is lazy, the grouping process will
849
+ * be executed once the resulting iterator is materialized.
850
+ *
851
+ * A new iterator will be created, holding the reference to the original one.
852
+ * This means that the original iterator won't be consumed until the
853
+ * the new one is and that consuming one of them will consume the other as well.
854
+ *
855
+ * ```ts
856
+ * const iterator = new SmartIterator<number>([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
857
+ * const result = iterator.groupBy<string>((value) => value % 2 === 0 ? "even" : "odd");
858
+ *
859
+ * console.log(result.toObject()); // { odd: [1, 3, 5, 7, 9], even: [2, 4, 6, 8, 10] }
860
+ * ```
861
+ *
862
+ * @template K The type of the keys used to group the elements.
863
+ *
864
+ * @param iteratee The key function to apply to each element of the iterator.
865
+ *
866
+ * @returns A new instance of the {@link AggregatedIterator} class containing the grouped elements.
867
+ */
276
868
  public groupBy<K extends PropertyKey>(iteratee: Iteratee<T, K>): AggregatedIterator<K, T>
277
869
  {
278
870
  return new AggregatedIterator(this.map((element, index) =>
@@ -283,6 +875,24 @@ export default class SmartIterator<T, R = void, N = undefined> implements Iterat
283
875
  }));
284
876
  }
285
877
 
878
+ /**
879
+ * Materializes the iterator into an array.
880
+ * This method will consume the entire iterator in the process.
881
+ *
882
+ * If the iterator is infinite, the method will never return.
883
+ *
884
+ * ```ts
885
+ * const iterator = new SmartIterator(function* ()
886
+ * {
887
+ * for (let i = 0; i < 5; i += 1) { yield i; }
888
+ * });
889
+ * const result = iterator.toArray();
890
+ *
891
+ * console.log(result); // [0, 1, 2, 3, 4]
892
+ * ```
893
+ *
894
+ * @returns The {@link Array} containing all elements of the iterator.
895
+ */
286
896
  public toArray(): T[]
287
897
  {
288
898
  return Array.from(this as Iterable<T>);