@byloth/core 2.0.0-rc.8 → 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 +3372 -609
  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 +140 -5
  16. package/src/models/callbacks/switchable-callback.ts +143 -5
  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
@@ -9,24 +9,192 @@ import type {
9
9
  import type { MaybePromise } from "../types.js";
10
10
 
11
11
  import ReducedIterator from "./reduced-iterator.js";
12
- import type { MaybeAsyncKeyedIteratee, MaybeAsyncKeyedTypeGuardIteratee, MaybeAsyncKeyedReducer } from "./types.js";
13
-
12
+ import type { MaybeAsyncKeyedIteratee, MaybeAsyncKeyedReducer } from "./types.js";
13
+
14
+ /**
15
+ * A class representing an iterator that aggregates elements in a lazy and optimized way.
16
+ *
17
+ * It's part of the {@link SmartAsyncIterator} implementation,
18
+ * providing a way to group elements of an iterable by key.
19
+ * For this reason, it isn't recommended to instantiate this class directly
20
+ * (although it's still possible), but rather use the {@link SmartAsyncIterator.groupBy} method.
21
+ *
22
+ * It isn't directly iterable like its parent class but rather needs to specify on what you want to iterate.
23
+ * See the {@link AggregatedAsyncIterator.keys}, {@link AggregatedAsyncIterator.entries}
24
+ * & {@link AggregatedAsyncIterator.values} methods.
25
+ * It does, however, provide the same set of methods to perform
26
+ * operations and transformations on the elements of the iterator,
27
+ * having also the knowledge and context of the groups to which
28
+ * they belong, allowing to handle them in a grouped manner.
29
+ *
30
+ * This is particularly useful when you need to group elements and
31
+ * then perform specific operations on the groups themselves.
32
+ *
33
+ * ```ts
34
+ * const elements = fetch([...]); // Promise<[-3, -1, 0, 2, 3, 5, 6, 8]>;
35
+ * const results = new SmartAsyncIterator(elements)
36
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd")
37
+ * .count();
38
+ *
39
+ * console.log(await results.toObject()); // { odd: 4, even: 4 }
40
+ * ```
41
+ *
42
+ * @template K The type of the keys used to group the elements.
43
+ * @template T The type of the elements to aggregate.
44
+ */
14
45
  export default class AggregatedAsyncIterator<K extends PropertyKey, T>
15
46
  {
47
+ /**
48
+ * The internal {@link SmartAsyncIterator} object that holds the elements to aggregate.
49
+ */
16
50
  protected _elements: SmartAsyncIterator<[K, T]>;
17
51
 
52
+ /**
53
+ * Initializes a new instance of the {@link AggregatedAsyncIterator} class.
54
+ *
55
+ * ```ts
56
+ * const iterator = new AggregatedAsyncIterator<string, number>([["A", 1], ["B", 2], ["A", 3], ["C", 4], ["B", 5]]);
57
+ * ```
58
+ *
59
+ * @param iterable The iterable to aggregate.
60
+ */
18
61
  public constructor(iterable: Iterable<[K, T]>);
62
+
63
+ /**
64
+ * Initializes a new instance of the {@link AggregatedAsyncIterator} class.
65
+ *
66
+ * ```ts
67
+ * const elements = fetch([...]); // Promise<[["A", 1], ["B", 2], ["A", 3], ["C", 4], ["B", 5]]>
68
+ * const iterator = new AggregatedAsyncIterator<string, number>(elements);
69
+ * ```
70
+ *
71
+ * @param iterable The iterable to aggregate.
72
+ */
19
73
  public constructor(iterable: AsyncIterable<[K, T]>);
74
+
75
+ /**
76
+ * Initializes a new instance of the {@link AggregatedAsyncIterator} class.
77
+ *
78
+ * ```ts
79
+ * import { Random } from "@byloth/core";
80
+ *
81
+ * const iterator = new AggregatedAsyncIterator<string, number>({
82
+ * _index: 0,
83
+ * next: () =>
84
+ * {
85
+ * if (this._index >= 5) { return { done: true, value: undefined }; }
86
+ * this._index += 1;
87
+ *
88
+ * return { done: false, value: [Random.Choice(["A", "B", "C"]), (this._index + 1)] };
89
+ * }
90
+ * });
91
+ * ```
92
+ *
93
+ * @param iterator The iterator to aggregate.
94
+ */
20
95
  public constructor(iterator: Iterator<[K, T]>);
96
+
97
+ /**
98
+ * Initializes a new instance of the {@link AggregatedAsyncIterator} class.
99
+ *
100
+ * ```ts
101
+ * import { Random } from "@byloth/core";
102
+ *
103
+ * const iterator = new AggregatedAsyncIterator<string, number>({
104
+ * _index: 0,
105
+ * next: async () =>
106
+ * {
107
+ * if (this._index >= 5) { return { done: true, value: undefined }; }
108
+ * this._index += 1;
109
+ *
110
+ * return { done: false, value: [Random.Choice(["A", "B", "C"]), (this._index + 1)] };
111
+ * }
112
+ * });
113
+ * ```
114
+ *
115
+ * @param iterator The iterator to aggregate.
116
+ */
21
117
  public constructor(iterator: AsyncIterator<[K, T]>);
118
+
119
+ /**
120
+ * Initializes a new instance of the {@link AggregatedAsyncIterator} class.
121
+ *
122
+ * ```ts
123
+ * import { range, Random } from "@byloth/core";
124
+ *
125
+ * const iterator = new AggregatedAsyncIterator<string, number>(function* ()
126
+ * {
127
+ * for (const index of range(5))
128
+ * {
129
+ * yield [Random.Choice(["A", "B", "C"]), (index + 1)];
130
+ * }
131
+ * });
132
+ * ```
133
+ *
134
+ * @param generatorFn The generator function to aggregate.
135
+ */
22
136
  public constructor(generatorFn: GeneratorFunction<[K, T]>);
137
+
138
+ /**
139
+ * Initializes a new instance of the {@link AggregatedAsyncIterator} class.
140
+ *
141
+ * ```ts
142
+ * import { range, Random } from "@byloth/core";
143
+ *
144
+ * const iterator = new AggregatedAsyncIterator<string, number>(async function* ()
145
+ * {
146
+ * for await (const index of range(5))
147
+ * {
148
+ * yield [Random.Choice(["A", "B", "C"]), (index + 1)];
149
+ * }
150
+ * });
151
+ * ```
152
+ *
153
+ * @param generatorFn The generator function to aggregate.
154
+ */
23
155
  public constructor(generatorFn: AsyncGeneratorFunction<[K, T]>);
156
+
157
+ /**
158
+ * Initializes a new instance of the {@link AggregatedAsyncIterator} class.
159
+ *
160
+ * ```ts
161
+ * const iterator = new AggregatedAsyncIterator(asyncKeyedValues);
162
+ * ```
163
+ *
164
+ * @param argument The iterable, iterator or generator function to aggregate.
165
+ */
24
166
  public constructor(argument: MaybeAsyncIteratorLike<[K, T]> | MaybeAsyncGeneratorFunction<[K, T]>);
25
167
  public constructor(argument: MaybeAsyncIteratorLike<[K, T]> | MaybeAsyncGeneratorFunction<[K, T]>)
26
168
  {
27
169
  this._elements = new SmartAsyncIterator(argument);
28
170
  }
29
171
 
172
+ /**
173
+ * Determines whether all elements of each group of the iterator satisfy a given condition.
174
+ * See also {@link AggregatedAsyncIterator.some}.
175
+ * This method will consume the entire iterator in the process.
176
+ *
177
+ * It will iterate over all elements of the iterator checjing if they satisfy the condition.
178
+ * Once a single element of one group doesn't satisfy the condition,
179
+ * the result for the respective group will be `false`.
180
+ *
181
+ * Eventually, it will return a new {@link ReducedIterator}
182
+ * object that will contain all the boolean results for each group.
183
+ * If the iterator is infinite, the method will never return.
184
+ *
185
+ * ```ts
186
+ * const results = new SmartAsyncIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
187
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd")
188
+ * .every(async (key, value) => value >= 0);
189
+ *
190
+ * console.log(await results.toObject()); // { odd: false, even: true }
191
+ * ```
192
+ *
193
+ * @param predicate The condition to check for each element of the iterator.
194
+ *
195
+ * @returns
196
+ * A {@link Promise} resolving to a new {@link ReducedIterator} containing the boolean results for each group.
197
+ */
30
198
  public async every(predicate: MaybeAsyncKeyedIteratee<K, T, boolean>): Promise<ReducedIterator<K, boolean>>
31
199
  {
32
200
  const values = new Map<K, [number, boolean]>();
@@ -45,6 +213,33 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
45
213
  for (const [key, [_, result]] of values) { yield [key, result]; }
46
214
  });
47
215
  }
216
+
217
+ /**
218
+ * Determines whether any element of each group of the iterator satisfies a given condition.
219
+ * See also {@link AggregatedAsyncIterator.every}.
220
+ * This method will consume the entire iterator in the process.
221
+ *
222
+ * It will iterate over all elements of the iterator checjing if they satisfy the condition.
223
+ * Once a single element of one group satisfies the condition,
224
+ * the result for the respective group will be `true`.
225
+ *
226
+ * Eventually, it will return a new {@link ReducedIterator}
227
+ * object that will contain all the boolean results for each group.
228
+ * If the iterator is infinite, the method will never return.
229
+ *
230
+ * ```ts
231
+ * const results = new SmartAsyncIterator<number>([-5, -4, -3, -2, -1, 0])
232
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd")
233
+ * .some(async (key, value) => value >= 0);
234
+ *
235
+ * console.log(await results.toObject()); // { odd: false, even: true }
236
+ * ```
237
+ *
238
+ * @param predicate The condition to check for each element of the iterator.
239
+ *
240
+ * @returns
241
+ * A {@link Promise} resolving to a new {@link ReducedIterator} containing the boolean results for each group.
242
+ */
48
243
  public async some(predicate: MaybeAsyncKeyedIteratee<K, T, boolean>): Promise<ReducedIterator<K, boolean>>
49
244
  {
50
245
  const values = new Map<K, [number, boolean]>();
@@ -64,8 +259,59 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
64
259
  });
65
260
  }
66
261
 
262
+ /**
263
+ * Filters the elements of the iterator based on a given condition.
264
+ *
265
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
266
+ * If the condition is met, the element will be included in the new iterator.
267
+ *
268
+ * A new iterator will be created, holding the reference to the original one.
269
+ * This means that the original iterator won't be consumed until the
270
+ * new one is and that consuming one of them will consume the other as well.
271
+ *
272
+ * ```ts
273
+ * const results = new SmartAsyncIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
274
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd")
275
+ * .filter(async (key, value) => value >= 0);
276
+ *
277
+ * console.log(await results.toObject()); // { odd: [3, 5], even: [0, 2, 6, 8] }
278
+ * ```
279
+ *
280
+ * @param predicate The condition to check for each element of the iterator.
281
+ *
282
+ * @returns A new {@link AggregatedAsyncIterator} containing the elements that satisfy the condition.
283
+ */
67
284
  public filter(predicate: MaybeAsyncKeyedIteratee<K, T, boolean>): AggregatedAsyncIterator<K, T>;
68
- public filter<S extends T>(predicate: MaybeAsyncKeyedTypeGuardIteratee<K, T, S>): AggregatedAsyncIterator<K, S>;
285
+
286
+ /**
287
+ * Filters the elements of the iterator based on a given condition.
288
+ *
289
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
290
+ * If the condition is met, the element will be included in the new iterator.
291
+ *
292
+ * A new iterator will be created, holding the reference to the original one.
293
+ * This means that the original iterator won't be consumed until the
294
+ * new one is and that consuming one of them will consume the other as well.
295
+ *
296
+ * ```ts
297
+ * const results = new SmartAsyncIterator<number>([-3, "-1", 0, "2", "3", 5, 6, "8"])
298
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd")
299
+ * .filter<number>(async (key, value) => typeof value === "number");
300
+ *
301
+ * console.log(await results.toObject()); // { odd: [-3, 5], even: [0, 6] }
302
+ * ```
303
+ *
304
+ * @template S
305
+ * The type of the elements that satisfy the condition.
306
+ * This allows the type-system to infer the correct type of the new iterator.
307
+ *
308
+ * It must be a subtype of the original type of the elements.
309
+ *
310
+ * @param predicate The type guard condition to check for each element of the iterator.
311
+ *
312
+ * @returns A new {@link AggregatedAsyncIterator} containing the elements that satisfy the condition.
313
+ */
314
+ public filter<S extends T>(predicate: MaybeAsyncKeyedIteratee<K, T, boolean>): AggregatedAsyncIterator<K, S>;
69
315
  public filter(predicate: MaybeAsyncKeyedIteratee<K, T, boolean>): AggregatedAsyncIterator<K, T>
70
316
  {
71
317
  const elements = this._elements;
@@ -73,17 +319,43 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
73
319
  return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[K, T]>
74
320
  {
75
321
  const indexes = new Map<K, number>();
76
-
77
322
  for await (const [key, element] of elements)
78
323
  {
79
324
  const index = indexes.get(key) ?? 0;
80
-
81
325
  if (await predicate(key, element, index)) { yield [key, element]; }
82
326
 
83
327
  indexes.set(key, index + 1);
84
328
  }
85
329
  });
86
330
  }
331
+
332
+ /**
333
+ * Maps the elements of the iterator using a given transformation function.
334
+ *
335
+ * This method will iterate over all elements of the iterator applying the condition.
336
+ * The result of each transformation will be included in the new iterator.
337
+ *
338
+ * Since the iterator is lazy, the mapping process will
339
+ * be executed once the resulting iterator is materialized.
340
+ *
341
+ * A new iterator will be created, holding the reference to the original one.
342
+ * This means that the original iterator won't be consumed until the
343
+ * new one is and that consuming one of them will consume the other as well.
344
+ *
345
+ * ```ts
346
+ * const results = new SmartAsyncIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
347
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd")
348
+ * .map(async (key, value) => Math.abs(value));
349
+ *
350
+ * console.log(await results.toObject()); // { odd: [3, 1, 3, 5], even: [0, 2, 6, 8] }
351
+ * ```
352
+ *
353
+ * @template V The type of the elements after the transformation.
354
+ *
355
+ * @param iteratee The transformation function to apply to each element of the iterator.
356
+ *
357
+ * @returns A new {@link AggregatedAsyncIterator} containing the transformed elements.
358
+ */
87
359
  public map<V>(iteratee: MaybeAsyncKeyedIteratee<K, T, V>): AggregatedAsyncIterator<K, V>
88
360
  {
89
361
  const elements = this._elements;
@@ -91,22 +363,113 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
91
363
  return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[K, V]>
92
364
  {
93
365
  const indexes = new Map<K, number>();
94
-
95
366
  for await (const [key, element] of elements)
96
367
  {
97
368
  const index = indexes.get(key) ?? 0;
98
-
99
369
  yield [key, await iteratee(key, element, index)];
100
370
 
101
371
  indexes.set(key, index + 1);
102
372
  }
103
373
  });
104
374
  }
375
+
376
+ /**
377
+ * Reduces the elements of the iterator using a given reducer function.
378
+ * This method will consume the entire iterator in the process.
379
+ *
380
+ * It will iterate over all elements of the iterator applying the reducer function.
381
+ * The result of each iteration will be passed as the accumulator to the next one.
382
+ *
383
+ * The first accoumulator value will be the first element of the iterator.
384
+ * The last accumulator value will be the final result of the reduction.
385
+ *
386
+ * Eventually, it will return a new {@link ReducedIterator}
387
+ * object that will contain all the reduced results for each group.
388
+ * If the iterator is infinite, the method will never return.
389
+ *
390
+ * ```ts
391
+ * const results = new SmartAsyncIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
392
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd")
393
+ * .reduce(async (key, accumulator, value) => accumulator + value);
394
+ *
395
+ * console.log(await results.toObject()); // { odd: 4, even: 16 }
396
+ * ```
397
+ *
398
+ * @param reducer The reducer function to apply to each element of the iterator.
399
+ *
400
+ * @returns
401
+ * A {@link Promise} resolving to a new {@link ReducedIterator} containing the reduced results for each group.
402
+ */
105
403
  public async reduce(reducer: MaybeAsyncKeyedReducer<K, T, T>): Promise<ReducedIterator<K, T>>;
404
+
405
+ /**
406
+ * Reduces the elements of the iterator using a given reducer function.
407
+ * This method will consume the entire iterator in the process.
408
+ *
409
+ * It will iterate over all elements of the iterator applying the reducer function.
410
+ * The result of each iteration will be passed as the accumulator to the next one.
411
+ *
412
+ * The first accoumulator value will be the provided initial value.
413
+ * The last accumulator value will be the final result of the reduction.
414
+ *
415
+ * Eventually, it will return a new {@link ReducedIterator}
416
+ * object that will contain all the reduced results for each group.
417
+ * If the iterator is infinite, the method will never return.
418
+ *
419
+ * ```ts
420
+ * const results = new SmartAsyncIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
421
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd")
422
+ * .reduce(async (key, accumulator, value) => accumulator + value, 0);
423
+ *
424
+ * console.log(await results.toObject()); // { odd: 4, even: 16 }
425
+ * ```
426
+ *
427
+ * @template A The type of the accumulator value which will also be the final result of the reduction.
428
+ *
429
+ * @param reducer The reducer function to apply to each element of the iterator.
430
+ * @param initialValue The initial value for the accumulator.
431
+ *
432
+ * @returns
433
+ * A {@link Promise} resolving to a new {@link ReducedIterator} containing the reduced results for each group.
434
+ */
435
+ public async reduce<A extends PropertyKey>(reducer: MaybeAsyncKeyedReducer<K, T, A>, initialValue: MaybePromise<A>)
436
+ : Promise<ReducedIterator<K, A>>;
437
+
438
+ /**
439
+ * Reduces the elements of the iterator using a given reducer function.
440
+ * This method will consume the entire iterator in the process.
441
+ *
442
+ * It will iterate over all elements of the iterator applying the reducer function.
443
+ * The result of each iteration will be passed as the accumulator to the next one.
444
+ *
445
+ * The first accoumulator value will be the provided initial value by the given function.
446
+ * The last accumulator value will be the final result of the reduction.
447
+ *
448
+ * Eventually, it will return a new {@link ReducedIterator}
449
+ * object that will contain all the reduced results for each group.
450
+ * If the iterator is infinite, the method will never return.
451
+ *
452
+ * ```ts
453
+ * const results = new SmartAsyncIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
454
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd")
455
+ * .reduce(async (key, { value }, currentValue) => ({ value: value + currentValue }), (key) => ({ value: 0 }));
456
+ *
457
+ * console.log(await results.toObject()); // { odd: { value: 4 }, even: { value: 16 } }
458
+ * ```
459
+ *
460
+ * @template A The type of the accumulator value which will also be the final result of the reduction.
461
+ *
462
+ * @param reducer The reducer function to apply to each element of the iterator.
463
+ * @param initialValue The function that provides the initial value for the accumulator.
464
+ *
465
+ * @returns
466
+ * A {@link Promise} resolving to a new {@link ReducedIterator} containing the reduced results for each group.
467
+ */
106
468
  public async reduce<A>(reducer: MaybeAsyncKeyedReducer<K, T, A>, initialValue: (key: K) => MaybePromise<A>)
107
469
  : Promise<ReducedIterator<K, A>>;
108
- public async reduce<A>(reducer: MaybeAsyncKeyedReducer<K, T, A>, initialValue?: (key: K) => MaybePromise<A>)
109
- : Promise<ReducedIterator<K, A>>
470
+ public async reduce<A>(
471
+ reducer: MaybeAsyncKeyedReducer<K, T, A>, initialValue?: MaybePromise<A> | ((key: K) => MaybePromise<A>)
472
+ ): Promise<ReducedIterator<K, A>>
110
473
  {
111
474
  const values = new Map<K, [number, A]>();
112
475
 
@@ -119,7 +482,9 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
119
482
  else if (initialValue !== undefined)
120
483
  {
121
484
  index = 0;
122
- accumulator = await initialValue(key);
485
+
486
+ if (initialValue instanceof Function) { accumulator = await initialValue(key); }
487
+ else { accumulator = await initialValue; }
123
488
  }
124
489
  else
125
490
  {
@@ -137,26 +502,84 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
137
502
  });
138
503
  }
139
504
 
140
- public flatMap<V>(iteratee: MaybeAsyncKeyedIteratee<K, T, Iterable<V>>): AggregatedAsyncIterator<K, V>
505
+ /**
506
+ * Flattens the elements of the iterator using a given transformation function.
507
+ *
508
+ * This method will iterate over all elements of the iterator applying the transformation function.
509
+ * The result of each transformation will be included in the new iterator.
510
+ *
511
+ * Since the iterator is lazy, the mapping process will
512
+ * be executed once the resulting iterator is materialized.
513
+ *
514
+ * A new iterator will be created, holding the reference to the original one.
515
+ * This means that the original iterator won't be consumed until the
516
+ * new one is and that consuming one of them will consume the other as well.
517
+ *
518
+ * ```ts
519
+ * const results = new SmartAsyncIterator<number>([[-3, -1], 0, 2, 3, 5, [6, 8]])
520
+ * .groupBy(async (values) =>
521
+ * {
522
+ * const value = values instanceof Array ? values[0] : values;
523
+ * return value % 2 === 0 ? "even" : "odd";
524
+ * })
525
+ * .flatMap(async (key, values) => values);
526
+ *
527
+ * console.log(await results.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }
528
+ * ```
529
+ *
530
+ * @template V The type of the elements after the transformation.
531
+ *
532
+ * @param iteratee The transformation function to apply to each element of the iterator.
533
+ *
534
+ * @returns A new {@link AggregatedAsyncIterator} containing the transformed elements.
535
+ */
536
+ public flatMap<V>(iteratee: MaybeAsyncKeyedIteratee<K, T, V | readonly V[]>): AggregatedAsyncIterator<K, V>
141
537
  {
142
538
  const elements = this._elements;
143
539
 
144
540
  return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[K, V]>
145
541
  {
146
542
  const indexes = new Map<K, number>();
147
-
148
543
  for await (const [key, element] of elements)
149
544
  {
150
545
  const index = indexes.get(key) ?? 0;
151
546
  const values = await iteratee(key, element, index);
152
547
 
153
- for await (const value of values) { yield [key, value]; }
548
+ if (values instanceof Array)
549
+ {
550
+ for (const value of values) { yield [key, value]; }
551
+ }
552
+ else { yield [key, values]; }
154
553
 
155
554
  indexes.set(key, index + 1);
156
555
  }
157
556
  });
158
557
  }
159
558
 
559
+ /**
560
+ * Drops a given number of elements from the beginning of each group of the iterator.
561
+ * The remaining elements will be included in the new iterator.
562
+ * See also {@link AggregatedAsyncIterator.take}.
563
+ *
564
+ * Since the iterator is lazy, the dropping process will
565
+ * be executed once the resulting iterator is materialized.
566
+ *
567
+ * A new iterator will be created, holding the reference to the original one.
568
+ * This means that the original iterator won't be consumed until the
569
+ * new one is and that consuming one of them will consume the other as well.
570
+ *
571
+ * ```ts
572
+ * const results = new SmartAsyncIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
573
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd")
574
+ * .drop(2);
575
+ *
576
+ * console.log(await results.toObject()); // { odd: [3, 5], even: [6, 8] }
577
+ * ```
578
+ *
579
+ * @param count The number of elements to drop from the beginning of each group.
580
+ *
581
+ * @returns A new {@link AggregatedAsyncIterator} containing the remaining elements.
582
+ */
160
583
  public drop(count: number): AggregatedAsyncIterator<K, T>
161
584
  {
162
585
  const elements = this._elements;
@@ -164,7 +587,6 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
164
587
  return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[K, T]>
165
588
  {
166
589
  const indexes = new Map<K, number>();
167
-
168
590
  for await (const [key, element] of elements)
169
591
  {
170
592
  const index = indexes.get(key) ?? 0;
@@ -179,6 +601,31 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
179
601
  }
180
602
  });
181
603
  }
604
+
605
+ /**
606
+ * Takes a given number of elements from the beginning of each group of the iterator.
607
+ * The elements will be included in the new iterator.
608
+ * See also {@link AggregatedAsyncIterator.drop}.
609
+ *
610
+ * Since the iterator is lazy, the taking process will
611
+ * be executed once the resulting iterator is materialized.
612
+ *
613
+ * A new iterator will be created, holding the reference to the original one.
614
+ * This means that the original iterator won't be consumed until the
615
+ * new one is and that consuming one of them will consume the other as well.
616
+ *
617
+ * ```ts
618
+ * const results = new SmartAsyncIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
619
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd")
620
+ * .take(2);
621
+ *
622
+ * console.log(await results.toObject()); // { odd: [-3, -1], even: [0, 2] }
623
+ * ```
624
+ *
625
+ * @param count The number of elements to take from the beginning of each group.
626
+ *
627
+ * @returns A new {@link AggregatedAsyncIterator} containing the taken elements.
628
+ */
182
629
  public take(limit: number): AggregatedAsyncIterator<K, T>
183
630
  {
184
631
  const elements = this._elements;
@@ -186,7 +633,6 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
186
633
  return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[K, T]>
187
634
  {
188
635
  const indexes = new Map<K, number>();
189
-
190
636
  for await (const [key, element] of elements)
191
637
  {
192
638
  const index = indexes.get(key) ?? 0;
@@ -199,8 +645,67 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
199
645
  });
200
646
  }
201
647
 
648
+ /**
649
+ * Finds the first element of each group of the iterator that satisfies a given condition.
650
+ * This method will consume the entire iterator in the process.
651
+ *
652
+ * It will iterate over all elements of the iterator checking if they satisfy the condition.
653
+ * Once the first element of one group satisfies the condition,
654
+ * the result for the respective group will be the element itself.
655
+ *
656
+ * Eventually, it will return a new {@link ReducedIterator}
657
+ * object that will contain the first element that satisfies the condition for each group.
658
+ * If the iterator is infinite, the method will never return.
659
+ *
660
+ * ```ts
661
+ * const results = new SmartAsyncIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
662
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd")
663
+ * .find(async (key, value) => value > 0);
664
+ *
665
+ * console.log(await results.toObject()); // { odd: 3, even: 2 }
666
+ * ```
667
+ *
668
+ * @param predicate The condition to check for each element of the iterator.
669
+ *
670
+ * @returns
671
+ * A {@link Promise} resolving to a new {@link ReducedIterator} containing
672
+ * the first element that satisfies the condition for each group.
673
+ */
202
674
  public async find(predicate: MaybeAsyncKeyedIteratee<K, T, boolean>): Promise<ReducedIterator<K, T | undefined>>;
203
- public async find<S extends T>(predicate: MaybeAsyncKeyedTypeGuardIteratee<K, T, S>)
675
+
676
+ /**
677
+ * Finds the first element of each group of the iterator that satisfies a given condition.
678
+ * This method will consume the entire iterator in the process.
679
+ *
680
+ * It will iterate over all elements of the iterator checking if they satisfy the condition.
681
+ * Once the first element of one group satisfies the condition,
682
+ * the result for the respective group will be the element itself.
683
+ *
684
+ * Eventually, it will return a new {@link ReducedIterator}
685
+ * object that will contain the first element that satisfies the condition for each group.
686
+ * If the iterator is infinite, the method will never return.
687
+ *
688
+ * ```ts
689
+ * const results = new SmartAsyncIterator<number | string>([-3, "-1", 0, "2", "3", 5, 6, "8"])
690
+ * .groupBy(async (value) => Number(value) % 2 === 0 ? "even" : "odd")
691
+ * .find<number>(async (key, value) => typeof value === "number");
692
+ *
693
+ * console.log(await results.toObject()); // { odd: -3, even: 0 }
694
+ * ```
695
+ *
696
+ * @template S
697
+ * The type of the elements that satisfy the condition.
698
+ * This allows the type-system to infer the correct type of the new iterator.
699
+ *
700
+ * It must be a subtype of the original type of the elements.
701
+ *
702
+ * @param predicate The type guard condition to check for each element of the iterator.
703
+ *
704
+ * @returns
705
+ * A {@link Promise} resolving to a new {@link ReducedIterator} containing
706
+ * the first element that satisfies the condition for each group.
707
+ */
708
+ public async find<S extends T>(predicate: MaybeAsyncKeyedIteratee<K, T, boolean>)
204
709
  : Promise<ReducedIterator<K, S | undefined>>;
205
710
 
206
711
  public async find(predicate: MaybeAsyncKeyedIteratee<K, T, boolean>): Promise<ReducedIterator<K, T | undefined>>
@@ -223,6 +728,53 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
223
728
  });
224
729
  }
225
730
 
731
+ /**
732
+ * Enumerates the elements of the iterator.
733
+ * Each element is paired with its index within the group in the new iterator.
734
+ *
735
+ * Since the iterator is lazy, the enumeration process will
736
+ * be executed once the resulting iterator is materialized.
737
+ *
738
+ * A new iterator will be created, holding the reference to the original one.
739
+ * This means that the original iterator won't be consumed until the
740
+ * new one is and that consuming one of them will consume the other as well.
741
+ *
742
+ * ```ts
743
+ * const results = new SmartAsyncIterator<number>([-3, 0, 2, -1, 3])
744
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd")
745
+ * .enumerate();
746
+ *
747
+ * console.log(results.toObject()); // { odd: [[0, -3], [1, -1], [2, 3]], even: [[0, 0], [1, 2]] }
748
+ * ```
749
+ *
750
+ * @returns A new {@link AggregatedAsyncIterator} containing the enumerated elements.
751
+ */
752
+ public enumerate(): AggregatedAsyncIterator<K, [number, T]>
753
+ {
754
+ return this.map((key, value, index) => [index, value]);
755
+ }
756
+
757
+ /**
758
+ * Removes all duplicate elements from within each group of the iterator.
759
+ * The first occurrence of each element will be included in the new iterator.
760
+ *
761
+ * Since the iterator is lazy, the deduplication process will
762
+ * be executed once the resulting iterator is materialized.
763
+ *
764
+ * A new iterator will be created, holding the reference to the original one.
765
+ * This means that the original iterator won't be consumed until the
766
+ * new one is and that consuming one of them will consume the other as well.
767
+ *
768
+ * ```ts
769
+ * const results = new SmartAsyncIterator<number>([-3, -1, 0, 2, 3, 6, -3, -1, 0, 5, 6, 8, 0, 2])
770
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd")
771
+ * .unique();
772
+ *
773
+ * console.log(await results.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }
774
+ * ```
775
+ *
776
+ * @returns A new {@link AggregatedAsyncIterator} containing only the unique elements.
777
+ */
226
778
  public unique(): AggregatedAsyncIterator<K, T>
227
779
  {
228
780
  const elements = this._elements;
@@ -230,11 +782,9 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
230
782
  return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[K, T]>
231
783
  {
232
784
  const keys = new Map<K, Set<T>>();
233
-
234
785
  for await (const [key, element] of elements)
235
786
  {
236
787
  const values = keys.get(key) ?? new Set<T>();
237
-
238
788
  if (values.has(element)) { continue; }
239
789
 
240
790
  values.add(element);
@@ -245,6 +795,23 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
245
795
  });
246
796
  }
247
797
 
798
+ /**
799
+ * Counts the number of elements within each group of the iterator.
800
+ * This method will consume the entire iterator in the process.
801
+ *
802
+ * If the iterator is infinite, the method will never return.
803
+ *
804
+ * ```ts
805
+ * const results = new SmartAsyncIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
806
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd")
807
+ * .count();
808
+ *
809
+ * console.log(await results.toObject()); // { odd: 4, even: 4 }
810
+ * ```
811
+ *
812
+ * @returns
813
+ * A {@link Promise} resolving to a new {@link ReducedIterator} containing the number of elements for each group.
814
+ */
248
815
  public async count(): Promise<ReducedIterator<K, number>>
249
816
  {
250
817
  const counters = new Map<K, number>();
@@ -262,6 +829,27 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
262
829
  });
263
830
  }
264
831
 
832
+ /**
833
+ * Iterates over the elements of the iterator.
834
+ * The elements are passed to the given iteratee function along with their key and index within the group.
835
+ *
836
+ * This method will consume the entire iterator in the process.
837
+ * If the iterator is infinite, the method will never return.
838
+ *
839
+ * ```ts
840
+ * const aggregator = new SmartAsyncIterator<number>([-3, 0, 2, -1, 3])
841
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd");
842
+ *
843
+ * await aggregator.forEach(async (key, value, index) =>
844
+ * {
845
+ * console.log(`${index}: ${value}`); // "0: -3", "0: 0", "1: 2", "1: -1", "2: 3"
846
+ * };
847
+ * ```
848
+ *
849
+ * @param iteratee The function to execute for each element of the iterator.
850
+ *
851
+ * @returns A {@link Promise} that will resolve once the iteration is complete.
852
+ */
265
853
  public async forEach(iteratee: MaybeAsyncKeyedIteratee<K, T>): Promise<void>
266
854
  {
267
855
  const indexes = new Map<K, number>();
@@ -270,12 +858,77 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
270
858
  {
271
859
  const index = indexes.get(key) ?? 0;
272
860
 
273
- iteratee(key, element, index);
861
+ await iteratee(key, element, index);
274
862
 
275
863
  indexes.set(key, index + 1);
276
864
  }
277
865
  }
278
866
 
867
+ /**
868
+ * Changes the key of each element on which the iterator is aggregated.
869
+ * The new key is determined by the given iteratee function.
870
+ *
871
+ * Since the iterator is lazy, the reorganization process will
872
+ * be executed once the resulting iterator is materialized.
873
+ *
874
+ * A new iterator will be created, holding the reference to the original one.
875
+ * This means that the original iterator won't be consumed until the
876
+ * new one is and that consuming one of them will consume the other as well.
877
+ *
878
+ * ```ts
879
+ * const results = new SmartAsyncIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
880
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd")
881
+ * .map(async (key, value, index) => index % 2 === 0 ? value : -value)
882
+ * .reorganizeBy(async (key, value) => value >= 0 ? "+" : "-");
883
+ *
884
+ * console.log(await results.toObject()); // { "+": [1, 0, 3, 6], "-": [-3, -2, -5, -8] }
885
+ * ```
886
+ *
887
+ * @template J The type of the new key.
888
+ *
889
+ * @param iteratee The function to determine the new key for each element of the iterator.
890
+ *
891
+ * @returns A new {@link AggregatedAsyncIterator} containing the elements reorganized by the new keys.
892
+ */
893
+ public reorganizeBy<J extends PropertyKey>(iteratee: MaybeAsyncKeyedIteratee<K, T, J>)
894
+ : AggregatedAsyncIterator<J, T>
895
+ {
896
+ const elements = this._elements;
897
+
898
+ return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[J, T]>
899
+ {
900
+ const indexes = new Map<K, number>();
901
+ for await (const [key, element] of elements)
902
+ {
903
+ const index = indexes.get(key) ?? 0;
904
+ yield [await iteratee(key, element, index), element];
905
+
906
+ indexes.set(key, index + 1);
907
+ }
908
+ });
909
+ }
910
+
911
+ /**
912
+ * An utility method that returns a new {@link SmartAsyncIterator}
913
+ * object containing all the keys of the iterator.
914
+ *
915
+ * Since the iterator is lazy, the keys will be extracted
916
+ * be executed once the resulting iterator is materialized.
917
+ *
918
+ * A new iterator will be created, holding the reference to the original one.
919
+ * This means that the original iterator won't be consumed until the
920
+ * new one is and that consuming one of them will consume the other as well.
921
+ *
922
+ * ```ts
923
+ * const keys = new SmartAsyncIterator([-3, Symbol(), "A", { }, null, [1 , 2, 3], false])
924
+ * .groupBy(async (value) => typeof value)
925
+ * .keys();
926
+ *
927
+ * console.log(await keys.toArray()); // ["number", "symbol", "string", "object", "boolean"]
928
+ * ```
929
+ *
930
+ * @returns A new {@link SmartAsyncIterator} containing all the keys of the iterator.
931
+ */
279
932
  public keys(): SmartAsyncIterator<K>
280
933
  {
281
934
  const elements = this._elements;
@@ -283,7 +936,6 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
283
936
  return new SmartAsyncIterator<K>(async function* ()
284
937
  {
285
938
  const keys = new Set<K>();
286
-
287
939
  for await (const [key] of elements)
288
940
  {
289
941
  if (keys.has(key)) { continue; }
@@ -293,10 +945,55 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
293
945
  }
294
946
  });
295
947
  }
296
- public items(): SmartAsyncIterator<[K, T]>
948
+
949
+ /**
950
+ * An utility method that returns a new {@link SmartAsyncIterator}
951
+ * object containing all the entries of the iterator.
952
+ * Each entry is a tuple containing the key and the element.
953
+ *
954
+ * Since the iterator is lazy, the entries will be extracted
955
+ * be executed once the resulting iterator is materialized.
956
+ *
957
+ * A new iterator will be created, holding the reference to the original one.
958
+ * This means that the original iterator won't be consumed until the
959
+ * new one is and that consuming one of them will consume the other as well.
960
+ *
961
+ * ```ts
962
+ * const entries = new SmartAsyncIterator<number>([-3, 0, 2, -1, 3])
963
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd")
964
+ * .entries();
965
+ *
966
+ * console.log(await entries.toArray()); // [["odd", -3], ["even", 0], ["even", 2], ["odd", -1], ["odd", 3]]
967
+ * ```
968
+ *
969
+ * @returns A new {@link SmartAsyncIterator} containing all the entries of the iterator.
970
+ */
971
+ public entries(): SmartAsyncIterator<[K, T]>
297
972
  {
298
973
  return this._elements;
299
974
  }
975
+
976
+ /**
977
+ * An utility method that returns a new {@link SmartAsyncIterator}
978
+ * object containing all the values of the iterator.
979
+ *
980
+ * Since the iterator is lazy, the values will be extracted
981
+ * be executed once the resulting iterator is materialized.
982
+ *
983
+ * A new iterator will be created, holding the reference to the original one.
984
+ * This means that the original iterator won't be consumed until the
985
+ * new one is and that consuming one of them will consume the other as well.
986
+ *
987
+ * ```ts
988
+ * const values = new SmartAsyncIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
989
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd")
990
+ * .values();
991
+ *
992
+ * console.log(await values.toArray()); // [-3, -1, 0, 2, 3, 5, 6, 8]
993
+ * ```
994
+ *
995
+ * @returns A new {@link SmartAsyncIterator} containing all the values of the iterator.
996
+ */
300
997
  public values(): SmartAsyncIterator<T>
301
998
  {
302
999
  const elements = this._elements;
@@ -307,12 +1004,43 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
307
1004
  });
308
1005
  }
309
1006
 
1007
+ /**
1008
+ * Materializes the iterator into an array of arrays.
1009
+ * This method will consume the entire iterator in the process.
1010
+ *
1011
+ * If the iterator is infinite, the method will never return.
1012
+ *
1013
+ * ```ts
1014
+ * const aggregator = new SmartAsyncIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
1015
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd");
1016
+ *
1017
+ * console.log(await aggregator.toArray()); // [[-3, -1, 3, 5], [0, 2, 6, 8]]
1018
+ * ```
1019
+ *
1020
+ * @returns A {@link Promise} resolving to an {@link Array} containing all the values of the iterator.
1021
+ */
310
1022
  public async toArray(): Promise<T[][]>
311
1023
  {
312
1024
  const map = await this.toMap();
313
1025
 
314
1026
  return Array.from(map.values());
315
1027
  }
1028
+
1029
+ /**
1030
+ * Materializes the iterator into a map.
1031
+ * This method will consume the entire iterator in the process.
1032
+ *
1033
+ * If the iterator is infinite, the method will never return.
1034
+ *
1035
+ * ```ts
1036
+ * const aggregator = new SmartAsyncIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
1037
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd");
1038
+ *
1039
+ * console.log(await aggregator.toMap()); // Map(2) { "odd" => [-3, -1, 3, 5], "even" => [0, 2, 6, 8] }
1040
+ * ```
1041
+ *
1042
+ * @returns A {@link Promise} resolving to a {@link Map} containing all the entries of the iterator.
1043
+ */
316
1044
  public async toMap(): Promise<Map<K, T[]>>
317
1045
  {
318
1046
  const groups = new Map<K, T[]>();
@@ -327,6 +1055,22 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
327
1055
 
328
1056
  return groups;
329
1057
  }
1058
+
1059
+ /**
1060
+ * Materializes the iterator into an object.
1061
+ * This method will consume the entire iterator in the process.
1062
+ *
1063
+ * If the iterator is infinite, the method will never return.
1064
+ *
1065
+ * ```ts
1066
+ * const aggregator = new SmartAsyncIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
1067
+ * .groupBy(async (value) => value % 2 === 0 ? "even" : "odd");
1068
+ *
1069
+ * console.log(await aggregator.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }
1070
+ * ```
1071
+ *
1072
+ * @returns A {@link Promise} resolving to an object containing all the entries of the iterator.
1073
+ */
330
1074
  public async toObject(): Promise<Record<K, T[]>>
331
1075
  {
332
1076
  const groups = { } as Record<K, T[]>;