├── .editorconfig ├── .gitignore ├── .travis.yml ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── _config.yml ├── examples └── todos.ts ├── gulpfile.ts ├── lib ├── index.ts ├── iterators │ ├── concat.ts │ ├── distinct.ts │ ├── exclude.ts │ ├── filter.ts │ ├── flat.ts │ ├── groupJoin.ts │ ├── intersect.ts │ ├── join.ts │ ├── leftJoin.ts │ ├── map.ts │ ├── skip.ts │ ├── slice.ts │ └── take.ts ├── reducers │ ├── average.ts │ ├── awaitAll.ts │ ├── first.ts │ ├── forEach.ts │ ├── indexOf.ts │ ├── last.ts │ ├── lastIndexOf.ts │ ├── length.ts │ ├── max.ts │ ├── min.ts │ ├── nth.ts │ ├── reduce.ts │ └── sum.ts ├── types │ ├── AsyncIterableFilter.ts │ ├── AsyncIterableJoin.ts │ ├── AsyncIterablePredicate.ts │ ├── AsyncIterableQuery.ts │ ├── AsyncIterableSet.ts │ ├── AsyncIterableTransformation.ts │ └── AsyncIterableValue.ts └── utils │ ├── asyncIterable.ts │ ├── asyncIterator.ts │ └── isAsyncIterable.ts ├── package.json ├── test ├── helpers │ ├── SpyAsyncIterable.ts │ ├── SpyIterable.ts │ ├── asyncGenerators.ts │ └── toArray.ts ├── iterators │ ├── concat.test.ts │ ├── distinct.test.ts │ ├── exclude.test.ts │ ├── filter.test.ts │ ├── flat.test.ts │ ├── groupJoin.test.ts │ ├── intersect.test.ts │ ├── join.test.ts │ ├── leftJoin.test.ts │ ├── map.test.ts │ ├── skip.test.ts │ ├── slice.test.ts │ └── take.test.ts ├── itiriri │ ├── filter.test.ts │ ├── join.test.ts │ ├── predicate.test.ts │ ├── query.test.ts │ ├── set.test.ts │ ├── transformation.test.ts │ └── value.test.ts ├── itiririAsync.test.ts ├── reducers │ ├── average.test.ts │ ├── awaitAll.test.ts │ ├── first.test.ts │ ├── forEach.test.ts │ ├── indexOf.test.ts │ ├── last.test.ts │ ├── lastIndexOf.test.ts │ ├── length.test.ts │ ├── max.test.ts │ ├── min.test.ts │ ├── nth.test.ts │ ├── reduce.test.ts │ └── sum.test.ts └── utils │ ├── asyncIterator.test.ts │ └── isAsyncIterable.test.ts ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = true 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | build 4 | package-lock.json 5 | itiriri-async.min.js 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "lts/*" 4 | - "node" 5 | cache: 6 | directories: 7 | - "node_modules" 8 | install: 9 | - "npm install" 10 | - "gulp tslint" 11 | - "gulp test" 12 | script: "gulp coverage" 13 | after_script: "npm install coveralls@3.0.0 && cat ./coverage/lcov.info | coveralls" 14 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${file}", 12 | "outFiles": [ 13 | "${workspaceFolder}/**/*.js" 14 | ] 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "explorer.confirmDragAndDrop": false, 3 | "editor.tabSize": 2, 4 | "editor.renderWhitespace": "all", 5 | "editor.formatOnSave": true, 6 | "explorer.confirmDelete": false, 7 | "files.exclude": { 8 | "**/node_modules": true, 9 | "**/build": true, 10 | "**/coverage": true 11 | } 12 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Labs42 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # itiriri-async 2 | 3 | [![Build Status](https://travis-ci.org/labs42io/itiriri-async.svg)](https://travis-ci.org/labs42io/itiriri-async) 4 | [![Coverage Status](https://coveralls.io/repos/github/labs42io/itiriri-async/badge.svg)](https://coveralls.io/github/labs42io/itiriri-async) 5 | 6 | Next generation library to manipulate [asynchronous iterators](https://github.com/tc39/proposal-async-iteration). 7 | 8 | ```ts 9 | import * as WebRequest from 'web-request'; 10 | import itiririAsync from 'itiriri-async'; 11 | 12 | type ToDo = { 13 | id: number, 14 | userId: number, 15 | title: string, 16 | completed: boolean, 17 | }; 18 | 19 | async function* todosAsync() { 20 | let id = 1; 21 | while (true) { 22 | yield await WebRequest.json(`https://jsonplaceholder.typicode.com/todos/${id++}`); 23 | } 24 | } 25 | 26 | async function showTop2ToDos(): Promise { 27 | const todos = await itiririAsync(todosAsync()) 28 | .filter(x => !x.completed) 29 | .take(2) 30 | .awaitAll(); 31 | 32 | console.log(todos.toArray()); 33 | } 34 | 35 | showTop2ToDos(); 36 | // [ 'delectus aut autem', 'quis ut nam facilis et officia qui' ] 37 | ``` 38 | 39 | > Check examples folder for more 40 | 41 | ## Installation 42 | 43 | Using npm: 44 | 45 | ```javascript 46 | $ npm install 'itiriri-async' --save 47 | ``` 48 | 49 | Importing: 50 | 51 | ```javascript 52 | import itiririAsync from 'itiriri-async'; 53 | ``` 54 | 55 | ## Running tests 56 | 57 | ```javascript 58 | $ npm install 59 | $ npm test 60 | ``` 61 | 62 | --- 63 | 64 | ## Complete list of methods 65 | 66 | * [average](#average) 67 | * [awaitAll](#awaitall) 68 | * [concat](#concat) 69 | * [distinct](#distinct) 70 | * [entries](#entries) 71 | * [every](#every) 72 | * [exclude](#exclude) 73 | * [filter](#filter) 74 | * [find](#find) 75 | * [findIndex](#findindex) 76 | * [findLast](#findlast) 77 | * [findLastIndex](#findlastindex) 78 | * [first](#first) 79 | * [flat](#flat) 80 | * [forEach](#foreach) 81 | * [groupJoin](#groupjoin) 82 | * [includes](#includes) 83 | * [indexOf](#indexof) 84 | * [intersect](#intersect) 85 | * [join](#join) 86 | * [keys](#keys) 87 | * [last](#last) 88 | * [lastIndexOf](#lastindexof) 89 | * [leftJoin](#leftjoin) 90 | * [length](#length) 91 | * [map](#map) 92 | * [max](#max) 93 | * [min](#min) 94 | * [nth](#nth) 95 | * [prepend](#prepend) 96 | * [reduce](#reduce) 97 | * [skip](#skip) 98 | * [slice](#slice) 99 | * [some](#some) 100 | * [sum](#sum) 101 | * [take](#take) 102 | * [union](#union) 103 | * [values](#values) 104 | 105 | ### `average` 106 | 107 | Returns the average value. 108 | 109 | > Syntax 110 | 111 | ```ts 112 | average(): Promise; 113 | average(selector: (element: T, index: number) => number): Promise; 114 | ``` 115 | 116 | > Parameters 117 | * `selector` - *(optional)* a value transformer function to apply to each element 118 | 119 | For a sequence with no elements returns `undefined`. 120 | 121 | > Example 122 | 123 | ```ts 124 | import itiririAsync from 'itiriri-async'; 125 | 126 | async function* generator1() { 127 | yield* [41, 42, 43]; 128 | } 129 | 130 | async function* generator2() { 131 | yield* [{value: 1}, {value: 2}]; 132 | } 133 | 134 | itiririAsync(generator1()).average() // returns Promise<42> 135 | itiririAsync(generator2()).average(elem => elem.value) // returns Promise<1.5> 136 | ``` 137 | 138 | ### `awaitAll` 139 | 140 | Awaits for all elements an returns `IterableQuery`. 141 | The ruterned iterable is a sync [`itiriri`](https://npmjs.com/package/itiriri) iterable. 142 | 143 | > Syntax 144 | 145 | ```ts 146 | awaitAll(): Promise> 147 | ``` 148 | 149 | > Example 150 | 151 | ```ts 152 | import itiririAsync from 'itiriri-async'; 153 | 154 | async function* generator() { 155 | yield* [41, 40, 43]; 156 | } 157 | 158 | // ... 159 | const numbers = await itiririAsync(generator()).awaitAll(); 160 | // returns IterableQuery([41, 40, 43]) 161 | 162 | numbers.sort().toArray(); 163 | // returns: [40, 41, 43] 164 | ``` 165 | 166 | ### `concat` 167 | 168 | Concatenates the sequence with another one. 169 | 170 | > Syntax 171 | 172 | ```ts 173 | concat(other: T): AsyncIterableQuery; 174 | concat(other: Promise): AsyncIterableQuery; 175 | concat(other: Iterable): AsyncIterableQuery; 176 | concat(other: AsyncIterable): AsyncIterableQuery; 177 | ``` 178 | 179 | > Parameters 180 | * `other` - *(required)* sequence to concatenate 181 | 182 | > Example 183 | 184 | ```ts 185 | import itiririAsync from 'itiriri-async'; 186 | 187 | async function* generator1() { 188 | yield* [1, 2, 3]; 189 | } 190 | 191 | async function* generator2() { 192 | yield* [4, 5]; 193 | } 194 | 195 | (async function() { 196 | const q = await itiririAsync(generator1()).concat(generator2()).awaitAll(); 197 | q.toArray(); // returns [1, 2, 3, 4, 5] 198 | })(); 199 | 200 | (async function() { 201 | const q = await itiririAsync(generator1()).concat([2, 1]).awaitAll(); 202 | q.toArray(); // returns [1, 2, 3, 2, 1] 203 | })(); 204 | 205 | (async function() { 206 | const q = await itiririAsync(generator1()).concat(-1).awaitAll(); 207 | q.toArray(); // returns [1, 2, 3, -1] 208 | })(); 209 | ``` 210 | 211 | `concat` *is a deferred method and is executed only when the result sequence is iterated.* 212 | 213 | ### `distinct` 214 | 215 | Returns a sequence of unique elements. 216 | 217 | > Syntax 218 | 219 | ```ts 220 | distinct(): AsyncIterableQuery; 221 | distinct(selector: (element: T) => S): AsyncIterableQuery; 222 | ``` 223 | 224 | > Parameters 225 | * `selector` - *(optional)* a value transformer function to be used for comparisons 226 | 227 | > Example 228 | 229 | ```ts 230 | import itiririAsync from 'itiriri-async'; 231 | 232 | async function* generator() { 233 | yield* [1, 2, 3, 3, 3, 4, 2]; 234 | } 235 | 236 | (async function () { 237 | const q = await itiririAsync(generator()).distinct().awaitAll(); 238 | q.toArray(); // returns [1, 2, 3, 4] 239 | })(); 240 | ``` 241 | 242 | `distinct` *is a deferred method and is executed only when the result sequence is iterated.* 243 | 244 | ### `entries` 245 | 246 | Returns a sequence of key/value pair for each element and its index. 247 | 248 | > Syntax 249 | 250 | ```ts 251 | entries(): AsyncIterableQuery<[number, T]>; 252 | ``` 253 | 254 | > Example 255 | 256 | ```ts 257 | import itiririAsync from 'itiriri-async'; 258 | 259 | async function* generator() { 260 | yield* ['Bob', 'Alice']; 261 | } 262 | 263 | (async function () { 264 | const q = await itiririAsync(generator()).entries().awaitAll(); 265 | q.toArray(); // returns [[0, 'Bob'], [1, 'Alice']] 266 | })(); 267 | ``` 268 | 269 | `entries` *is a deferred method and is executed only when the result sequence is iterated.* 270 | 271 | ### `every` 272 | 273 | Tests whether all the elements pass the predicate. 274 | 275 | > Syntax 276 | 277 | ```ts 278 | every(predicate: (element: T, index: number) => boolean): Promise; 279 | ``` 280 | 281 | > Parameters 282 | * `predicate` - *(required)* function to test for each element 283 | 284 | > Example 285 | 286 | ```ts 287 | import itiririAsync from 'itiriri-async'; 288 | 289 | async function* generator() { 290 | yield* [1, 4, 3, 0]; 291 | } 292 | 293 | (async function () { 294 | await itiririAsync(generator()).every(x => x >= 0); // true 295 | })(); 296 | 297 | (async function () { 298 | await itiririAsync(generator()).every(x => x > 0); // false 299 | })(); 300 | ``` 301 | 302 | ### `exclude` 303 | 304 | Returns a sequence of elements not contained in a given sequence. 305 | 306 | > Syntax 307 | 308 | ```ts 309 | exclude(others: Iterable): AsyncIterableQuery; 310 | exclude(others: Iterable, selector: (element: T) => S): AsyncIterableQuery; 311 | ``` 312 | 313 | > Parameters 314 | * `others` - *(required)* a sequence of elements to be excluded 315 | * `selector` - *(optional)* a value transformer function to be used for comparisons 316 | 317 | > Example 318 | 319 | ```ts 320 | import itiririAsync from 'itiriri-async'; 321 | 322 | async function* generator1() { 323 | yield* [2, 0, 1, 8, 2]; 324 | } 325 | 326 | async function* generator2() { 327 | yield* [{ id: 1 }, { id: 2 }]; 328 | } 329 | 330 | (async function () { 331 | const q = await itiririAsync(generator1()).exclude([0, 1]).awaitAll(); 332 | q.toArray(); // returns [2, 8, 2] 333 | })(); 334 | 335 | (async function () { 336 | const q = await itiririAsync(generator2()).exclude([{ id: 2 }], x => x.id).awaitAll(); 337 | q.toArray(); // returns [{id: 1}] 338 | })(); 339 | ``` 340 | 341 | `exclude` *is a deferred method and is executed only when the result sequence is iterated.* 342 | 343 | ### `filter` 344 | 345 | Returns a sequence of elements that pass the predicate. 346 | 347 | > Syntax 348 | 349 | ```ts 350 | filter(predicate: (element: T, index: number) => boolean): AsyncIterableQuery; 351 | ``` 352 | 353 | > Parameters 354 | * `predicate` - *(required)* function to test for each element 355 | 356 | > Example 357 | 358 | ```ts 359 | import itiririAsync from 'itiriri-async'; 360 | 361 | async function* generator() { 362 | yield* [1, 2, 3, 4, 5]; 363 | } 364 | 365 | (async function () { 366 | const q = await itiririAsync(generator()).filter(elem => elem < 3).awaitAll(); 367 | q.toArray(); // returns [1, 2] 368 | })(); 369 | 370 | (async function () { 371 | const q = await itiririAsync(generator()).filter(elem => elem > 10).awaitAll(); 372 | q.toArray(); // returns [] 373 | })(); 374 | ``` 375 | 376 | `filter` *is a deferred method and is executed only when the result sequence is iterated.* 377 | 378 | ### `find` 379 | 380 | Finds the first element that satisfies the specified predicate. 381 | 382 | > Syntax 383 | 384 | ```ts 385 | find(predicate: (element: T, index: number) => boolean): Promise; 386 | ``` 387 | 388 | > Parameters 389 | * `predicate` - *(required)* function to test for each element 390 | 391 | If no element satisfies the predicate, returns `undefined`. 392 | 393 | > Example 394 | 395 | ```ts 396 | import itiririAsync from 'itiriri-async'; 397 | 398 | async function* generator() { 399 | yield* [1, 2, 3, 4, 5]; 400 | } 401 | 402 | (async function () { 403 | await itiririAsync(generator()).find(elem => elem > 2); // returns 3 404 | })(); 405 | 406 | (async function () { 407 | await itiririAsync(generator()).find(elem => elem > 10); // returns undefined 408 | })(); 409 | ``` 410 | 411 | ### `findIndex` 412 | 413 | Finds the first index at which a given element satisfies the specified predicate. 414 | 415 | > Syntax 416 | 417 | ```ts 418 | findIndex(predicate: (element: T, index: number) => boolean): Promise; 419 | ``` 420 | 421 | > Parameters 422 | * `predicate` - *(required)* function to test for each element 423 | 424 | If no element satisfies the predicate, returns `-1`. 425 | 426 | > Example 427 | 428 | ```ts 429 | import itiririAsync from 'itiriri-async'; 430 | 431 | async function* generator() { 432 | yield* [1, 2, 3, 4, 5]; 433 | } 434 | 435 | (async function () { 436 | await itiririAsync(generator()).find(elem => elem > 2); // returns 2 437 | })(); 438 | 439 | (async function () { 440 | await itiririAsync(generator()).find(elem => elem > 10); // returns -1 441 | })(); 442 | ``` 443 | 444 | ### `findLast` 445 | 446 | Finds the last element that satisfies the specified predicate. 447 | 448 | > Syntax 449 | 450 | ```ts 451 | findLast(predicate: (element: T, index: number) => boolean): Promise; 452 | ``` 453 | 454 | > Parameters 455 | * `predicate` - *(required)* function to test for each element 456 | 457 | If no element satisfies the predicate, returns `undefined`. 458 | 459 | > Example 460 | 461 | ```ts 462 | import itiririAsync from 'itiriri-async'; 463 | 464 | async function* generator() { 465 | yield* [1, 2, 3, 4, 5]; 466 | } 467 | 468 | (async function () { 469 | await itiririAsync(generator()).findLast(elem => elem > 2); // returns 5 470 | })(); 471 | 472 | (async function () { 473 | await itiririAsync(generator()).findLast(elem => elem > 10); // returns undefined 474 | })(); 475 | ``` 476 | 477 | ### `findLastIndex` 478 | 479 | Finds the last index at which a given element satisfies the specified predicate. 480 | 481 | > Syntax 482 | 483 | ```ts 484 | findLastIndex(predicate: (element: T, index: number) => boolean): Promise; 485 | ``` 486 | 487 | > Parameters 488 | * `predicate` - *(required)* function to test for each element 489 | 490 | If not present, returns `-1`. 491 | 492 | > Example 493 | 494 | ```ts 495 | import itiririAsync from 'itiriri-async'; 496 | 497 | async function* generator() { 498 | yield* [1, 2, 3, 4, 5]; 499 | } 500 | 501 | (async function () { 502 | await itiririAsync(generator()).findLastIndex(elem => elem > 2); // returns 4 503 | })(); 504 | 505 | (async function () { 506 | await itiririAsync(generator()).findLastIndex(elem => elem > 10); // returns -1 507 | })(); 508 | ``` 509 | 510 | ### `first` 511 | 512 | Returns the first element in a sequence. 513 | 514 | > Syntax 515 | 516 | ```ts 517 | first(): Promise; 518 | ``` 519 | 520 | For an empty sequence returns `undefined`. 521 | 522 | > Example 523 | 524 | ```ts 525 | import itiririAsync from 'itiriri-async'; 526 | 527 | async function* generator1() { 528 | yield* [1, 2, 3, 4, 5]; 529 | } 530 | 531 | async function* generator2() { 532 | yield* []; 533 | } 534 | 535 | (async function () { 536 | await itiririAsync(generator1()).first(); // returns 1 537 | })(); 538 | 539 | (async function () { 540 | await itiririAsync(generator2()).first(); // returns undefined 541 | })(); 542 | ``` 543 | 544 | ### `flat` 545 | 546 | Returns a sequence with all sub-sequences concatenated. 547 | 548 | > Syntax 549 | 550 | ```ts 551 | flat(selector?: (element: T, index: number) => AsyncIterable): AsyncIterableQuery; 552 | ``` 553 | 554 | > Parameters 555 | * `selector` - *(optional)* a value transformer function to be used for comparisons 556 | 557 | > Example 558 | 559 | ```ts 560 | import itiririAsync from 'itiriri-async'; 561 | 562 | async function* generator() { 563 | yield* [[1, 2, 3], [4, 5]]; 564 | } 565 | 566 | (async function () { 567 | const q = await itiririAsync(generator()).flat().awaitAll(); 568 | q.toArray(); // returns [1, 2, 3, 4, 5] 569 | })(); 570 | ``` 571 | 572 | `flat` *is a deferred method and is executed only when the result sequence is iterated.* 573 | 574 | ### `forEach` 575 | 576 | Runs through every element and applies a given function. 577 | 578 | > Syntax 579 | 580 | ```ts 581 | forEach(action: (element: T, index: number) => void): Promise; 582 | ``` 583 | 584 | > Parameters 585 | * `action` - *(required)* function to apply on each element 586 | 587 | > Example 588 | 589 | ```ts 590 | import itiririAsync from 'itiriri-async'; 591 | 592 | async function* generator() { 593 | yield* [1, 2, 3]; 594 | } 595 | 596 | (async function () { 597 | await itiririAsync(generator()).forEach(elem => console.log(elem)); 598 | })(); 599 | // 1 600 | // 2 601 | // 3 602 | ``` 603 | 604 | ### `groupJoin` 605 | 606 | Returns a sequence of correlated elements where each element from the current sequence 607 | is matched with zero or more elements from the other sequence. 608 | 609 | > Syntax 610 | 611 | ```ts 612 | groupJoin( 613 | other: Iterable, 614 | leftKeySelector: (element: T, index: number) => TKey, 615 | rightKeySelector: (element: TRight, index: number) => TKey, 616 | joinSelector: (left: T, right: TRight[]) => TResult, 617 | ): AsyncIterableQuery; 618 | ``` 619 | 620 | > Parameters 621 | * `other` - *(required)* sequence to join 622 | * `leftKeySelector` - *(required)* function that provides the key of each element from source sequence 623 | * `rightKeySelector` - *(required)* function that provides the key of each element from joined sequence 624 | * `joinSelector` - *(required)* a transformation function to apply on each joined element with group 625 | 626 | The `joinSelector` function is called on each element from the source sequence and the array of matched 627 | elements from the joined sequence. 628 | When an element from the source sequence doesn't match with any of the elements from the joined sequence, 629 | the `joinSelector` function will be called with an empty array. 630 | 631 | > Example 632 | 633 | ```ts 634 | import itiririAsync from 'itiriri-async'; 635 | 636 | async function* generator() { 637 | yield* [1, 2, 3]; 638 | } 639 | 640 | (async function () { 641 | const q = await itiririAsync(generator()) 642 | .groupJoin([1, 2, 3, 1, 1, 2], x => x, x => x, (x, y) => ({ x, y })) 643 | .awaitAll(); 644 | q.toArray(); 645 | })(); 646 | //[ { x: 1, y: [ 1, 1, 1 ] }, 647 | // { x: 2, y: [ 2, 2 ] }, 648 | // { x: 3, y: [ 3 ] } ] 649 | ``` 650 | 651 | `groupJoin` *is a deferred method and is executed only when the result sequence is iterated.* 652 | 653 | ### `includes` 654 | 655 | Determines whether the sequence includes a certain element. 656 | 657 | > Syntax 658 | 659 | ```ts 660 | includes(element: T): Promise; 661 | includes(element: T, fromIndex: number): Promise; 662 | ``` 663 | 664 | > Parameters 665 | * `element` - *(required)* the element to search for 666 | * `fromIndex` - *(optional)* starting index, defaults to `0` 667 | 668 | `includes` uses triple equals `===` to compare elements. 669 | 670 | > Example 671 | 672 | ```ts 673 | import itiririAsync from 'itiriri-async'; 674 | 675 | async function* generator() { 676 | yield* [1, 2, 3]; 677 | } 678 | 679 | (async function () { 680 | await itiririAsync(generator()).includes(2); // returns: true 681 | await itiririAsync(generator()).includes(4); // returns: false 682 | })(); 683 | ``` 684 | 685 | ### `indexOf` 686 | 687 | Returns the first (zero-based) index at which a given element can be found. 688 | 689 | > Syntax 690 | 691 | ```ts 692 | indexOf(element: T): Promise; 693 | indexOf(element: T, fromIndex: number): Promise; 694 | ``` 695 | 696 | > Parameters 697 | * `element` - *(required)* the element to search for 698 | * `fromIndex` - *(optional)* starting index, defaults to `0` 699 | 700 | When an element is not found, returns -1. 701 | `indexOf` uses triple equals `===` to compare elements. 702 | 703 | > Example 704 | 705 | ```ts 706 | import itiririAsync from 'itiriri-async'; 707 | 708 | async function* generator() { 709 | yield* [1, 2, 3]; 710 | } 711 | 712 | (async function () { 713 | await itiririAsync(generator()).indexOf(2); // returns: 1 714 | await itiririAsync(generator()).indexOf(4); // returns: -1 715 | })(); 716 | ``` 717 | 718 | ### `intersect` 719 | 720 | Returns a set intersection with a given sequence. 721 | 722 | > Syntax 723 | 724 | ```ts 725 | intersect(others: Iterable): AsyncIterableQuery; 726 | intersect(other: Iterable, selector: (element: T) => S): AsyncIterableQuery; 727 | ``` 728 | 729 | > Parameters 730 | * `other` - *(required)* the sequence to intersect with 731 | * `selector` - *(optional)* a value transformer function to be used for comparisons 732 | 733 | > Example 734 | 735 | ```ts 736 | import itiririAsync from 'itiriri-async'; 737 | 738 | async function* generator() { 739 | yield* [1, 2, 3]; 740 | } 741 | 742 | (async function () { 743 | const q = await itiririAsync(generator()) 744 | .intersect([1, 2, 4]) 745 | .awaitAll(); 746 | q.toArray(); // returns: [1, 2] 747 | })(); 748 | ``` 749 | 750 | `intersect` *is a deferred method and is executed only when the result sequence is iterated.* 751 | 752 | ### `join` 753 | 754 | Returns a sequence of correlated elements transformation that match a given key. 755 | 756 | > Syntax 757 | 758 | ```ts 759 | join( 760 | other: Iterable, 761 | leftKeySelector: (element: T, index: number) => TKey, 762 | rightKeySelector: (element: TRight, index: number) => TKey, 763 | joinSelector: (left: T, right: TRight) => TResult, 764 | ): AsyncIterableQuery; 765 | ``` 766 | 767 | > Parameters 768 | * `other` - *(required)* sequence to join 769 | * `leftKeySelector` - *(required)* function that provides the key of each element from source sequence 770 | * `rightKeySelector` - *(required)* function that provides the key of each element from joined sequence 771 | * `joinSelector` - *(required)* a transformation function to apply on each matched tuple 772 | 773 | The `join` method works as an sql inner join. 774 | 775 | > Example 776 | 777 | ```ts 778 | import itiririAsync from 'itiriri-async'; 779 | 780 | async function* generator() { 781 | yield* [1, 2, 3]; 782 | } 783 | 784 | (async function () { 785 | const q = await itiririAsync(generator()) 786 | .join([1, 1, 2], x => x, x => x, (x, y) => ({ x, y })) 787 | .awaitAll(); 788 | q.toArray(); // returns: [ { x: 1, y: 1 }, { x: 1, y: 1 }, { x: 2, y: 2 } ] 789 | })(); 790 | ``` 791 | 792 | `join` *is a deferred method and is executed only when the result sequence is iterated.* 793 | 794 | ### `keys` 795 | 796 | Returns a sequence of keys for each index in the source sequence. 797 | 798 | > Syntax 799 | 800 | ```ts 801 | keys(): AsyncIterableQuery; 802 | ``` 803 | 804 | > Example 805 | 806 | ```ts 807 | import itiririAsync from 'itiriri-async'; 808 | 809 | async function* generator() { 810 | yield* ['a', 'b', 'c']; 811 | } 812 | 813 | (async function () { 814 | const q = await itiririAsync(generator()).keys().awaitAll(); 815 | q.toArray(); // returns: [0, 1, 2] 816 | })(); 817 | ``` 818 | 819 | `keys` *is a deferred method and is executed only when the result sequence is iterated.* 820 | 821 | ### `last` 822 | 823 | Returns the last element in a sequence. 824 | 825 | > Syntax 826 | 827 | ```ts 828 | last(): Promise; 829 | ``` 830 | 831 | For an empty sequence returns `undefined`. 832 | 833 | > Example 834 | 835 | ```ts 836 | import itiririAsync from 'itiriri-async'; 837 | 838 | async function* generator() { 839 | yield* [1, 2, 3, -2]; 840 | } 841 | 842 | (async function () { 843 | await itiririAsync(generator()).last(); // returns: -2 844 | })(); 845 | ``` 846 | 847 | ### `lastIndexOf` 848 | 849 | Returns the last index at which a given element can be found. 850 | 851 | > Syntax 852 | 853 | ```ts 854 | lastIndexOf(element: T): Promise; 855 | lastIndexOf(element: T, fromIndex: number): Promise; 856 | ``` 857 | 858 | > Parameters 859 | * `element` - *(required)* the element to search for 860 | * `fromIndex` - *(optional)* starting index, defaults to `0` 861 | 862 | When an element is not found, returns -1. 863 | `lastIndexOf` uses triple equals `===` to compare elements. 864 | 865 | > Example 866 | 867 | ```ts 868 | import itiririAsync from 'itiriri-async'; 869 | 870 | async function* generator() { 871 | yield* [1, 2, 3, 2, 1]; 872 | } 873 | 874 | (async function () { 875 | await itiririAsync(generator()).lastIndexOf(2); // returns: 3 876 | })(); 877 | ``` 878 | 879 | ### `leftJoin` 880 | 881 | Returns a sequence of correlated elements transformation that match a given key. 882 | 883 | > Syntax 884 | 885 | ```ts 886 | leftJoin( 887 | other: Iterable, 888 | leftKeySelector: (element: T, index: number) => TKey, 889 | rightKeySelector: (element: TRight, index: number) => TKey, 890 | joinSelector: (left: T, right?: TRight) => TResult, 891 | ): AsyncIterableQuery; 892 | ``` 893 | 894 | > Parameters 895 | * `other` - *(required)* sequence to join 896 | * `leftKeySelector` - *(required)* function that provides the key of each element from source sequence 897 | * `rightKeySelector` - *(required)* function that provides the key of each element from joined sequence 898 | * `joinSelector` - *(required)* a transformation function to apply on each matched tuple 899 | 900 | The `leftJoin` method works as an sql left join. 901 | When an element from the left sequence doesn't match with any of the elements from the right sequence, 902 | the `joinSelector` function is called with an `undefined` right value. 903 | 904 | > Example 905 | 906 | ```ts 907 | import itiririAsync from 'itiriri-async'; 908 | 909 | async function* generator() { 910 | yield* [1, 2, 3]; 911 | } 912 | 913 | (async function () { 914 | const q = await itiririAsync(generator()) 915 | .leftJoin([2, 3, 4, 2], n => n, n => n, (a, b) => `${a}-${b || '#'}`) 916 | .awaitAll(); 917 | q.toArray(); // returns ['1-#', '2-2', '2-2', '3-3'] 918 | })(); 919 | ``` 920 | 921 | `leftJoin` *is a deferred method and is executed only when the result sequence is iterated.* 922 | 923 | ### `length` 924 | 925 | Returns the number of elements in a sequence. 926 | 927 | > Syntax 928 | 929 | ```ts 930 | length(): Promise; 931 | length(predicate: (element: T, index: number) => boolean): Promise; 932 | ``` 933 | 934 | > Parameters 935 | * `predicate` - *(optional)* a function to count only the elements that match the predicate 936 | 937 | > Example 938 | 939 | ```ts 940 | import itiririAsync from 'itiriri-async'; 941 | 942 | async function* generator() { 943 | yield* [1, 2, 3]; 944 | } 945 | 946 | (async function () { 947 | await itiririAsync(generator()).length(); // returns 3 948 | })(); 949 | ``` 950 | 951 | ### `map` 952 | 953 | Returns a sequence of transformed values. 954 | 955 | > Syntax 956 | 957 | ```ts 958 | map(selector: (element: T, index: number) => S): AsyncIterableQuery; 959 | ``` 960 | 961 | > Parameters 962 | * `selector` - *(required)* a value transformer function to apply to each element 963 | 964 | > Example 965 | 966 | ```ts 967 | import itiririAsync from 'itiriri-async'; 968 | 969 | async function* generator() { 970 | yield* [1, 2, 3]; 971 | } 972 | 973 | function x10(numbers: AsyncIterable) { 974 | return itiririAsync(numbers).map(n => n * 10); 975 | } 976 | 977 | (async function(){ 978 | const numbers = await x10(generator()).awaitAll(); 979 | console.log(numbers); // [10, 20, 30] 980 | })(); 981 | ``` 982 | 983 | `map` *is a deferred method and is executed only when the result sequence is iterated.* 984 | 985 | ### `max` 986 | 987 | Returns the maximum element in a sequence. 988 | 989 | > Syntax 990 | 991 | ```ts 992 | max(): Promise; 993 | max(compareFn: (a: T, b: T) => number): Promise; 994 | ``` 995 | 996 | > Parameters 997 | * `compareFn` - *(optional)* a comparer function that compares two elements from a sequence and returns: 998 | * `-1` when `a` is less than `b` 999 | * `1` when `a` is greater `b` 1000 | * `0` when `a` equals to `b` 1001 | 1002 | If sequence is empty, returns `undefined`. 1003 | 1004 | > Example 1005 | 1006 | ```ts 1007 | async function* generator1() { 1008 | yield* [1, 42, 3]; 1009 | } 1010 | 1011 | async function* generator2() { 1012 | yield* []; 1013 | } 1014 | 1015 | 1016 | (async function () { 1017 | await itiririAsync(generator1()).max(); // returns 42 1018 | await itiririAsync(generator2()).max(); // returns undefined 1019 | })(); 1020 | ``` 1021 | 1022 | ### `min` 1023 | 1024 | Returns the minimum element in a sequence. 1025 | 1026 | > Syntax 1027 | 1028 | ```ts 1029 | min(): Promise; 1030 | min(compareFn: (a: T, b: T) => number): Promise; 1031 | ``` 1032 | 1033 | > Parameters 1034 | * `compareFn` - *(optional)* a comparer function that compares two elements from a sequence and returns: 1035 | * `-1` when `a` is less than `b` 1036 | * `1` when `a` is greater `b` 1037 | * `0` when `a` equals to `b` 1038 | 1039 | If sequence is empty, returns `undefined`. 1040 | 1041 | > Example 1042 | 1043 | ```ts 1044 | import itiririAsync from 'itiriri-async'; 1045 | 1046 | async function* generator1() { 1047 | yield* [1, -2, 3]; 1048 | } 1049 | 1050 | async function* generator2() { 1051 | yield* []; 1052 | } 1053 | 1054 | (async function () { 1055 | await itiririAsync(generator1()).min(); // returns -1 1056 | await itiririAsync(generator2()).min(); // returns undefined 1057 | })(); 1058 | ``` 1059 | 1060 | ### `nth` 1061 | 1062 | Returns the element at a specified index. 1063 | 1064 | > Syntax 1065 | 1066 | ```ts 1067 | nth(index: number): Promise; 1068 | ``` 1069 | 1070 | > Parameters 1071 | * `index` - *(required)* zero based index at which to get the element 1072 | 1073 | If index is out of the range, returns `undefined` . 1074 | 1075 | > Example 1076 | 1077 | ```ts 1078 | import itiririAsync from 'itiriri-async'; 1079 | 1080 | async function* generator() { 1081 | yield* [1, -2, 3]; 1082 | } 1083 | 1084 | (async function () { 1085 | await itiririAsync(generator()).nth(2); // returns: 3 1086 | await itiririAsync(generator()).nth(3); // returns: undefined 1087 | })(); 1088 | ``` 1089 | 1090 | ### `prepend` 1091 | 1092 | Returns a sequence with given elements at the beginning. 1093 | 1094 | > Syntax 1095 | 1096 | ```ts 1097 | prepend(other: T): AsyncIterableQuery; 1098 | prepend(other: Promise): AsyncIterableQuery; 1099 | prepend(other: Iterable): AsyncIterableQuery; 1100 | prepend(other: AsyncIterable): AsyncIterableQuery; 1101 | ``` 1102 | 1103 | > Parameters 1104 | * `other` - *(required)* the sequence to be added at the beginning 1105 | 1106 | > Example 1107 | 1108 | ```ts 1109 | import itiririAsync from 'itiriri-async'; 1110 | 1111 | async function* generator() { 1112 | yield* [1, -2, 3]; 1113 | } 1114 | 1115 | (async function () { 1116 | const q = await itiririAsync(generator()).prepend(4).awaitAll(); 1117 | q.toArray(); // returns: [4, 1, -2, 3] 1118 | })(); 1119 | 1120 | (async function () { 1121 | const q = await itiririAsync(generator()).prepend([0, 4]).awaitAll(); 1122 | q.toArray(); // returns: [0, 4, 1, -2, 3] 1123 | })(); 1124 | ``` 1125 | 1126 | `prepend` *is a deferred method and is executed only when the result sequence is iterated.* 1127 | 1128 | ### `reduce` 1129 | 1130 | Applies a function against an accumulator and each element *(from left to right)* to reduce it to a single value. 1131 | 1132 | > Syntax 1133 | 1134 | ```ts 1135 | reduce( 1136 | callback: (accumulator: T, current: T, index: number) => T, 1137 | ): Promise; 1138 | 1139 | reduce( 1140 | callback: (accumulator: S, current: T, index: number) => S, 1141 | initialValue: S, 1142 | ): Promise; 1143 | ``` 1144 | 1145 | > Parameters 1146 | * `callback` - *(required)* function to execute on each element in the sequence, taking three arguments 1147 | * `accumulator` the accumulator accumulates the callback's return values; 1148 | * `current` the current element being processed; 1149 | * `currentIndex` the index of the current element being processed; 1150 | * `initialValue` - *(optional)* value to use as the first argument to the first call of the `callback` 1151 | 1152 | Calling `reduce` on an empty sequence without an initial value throws an error. 1153 | 1154 | > Example 1155 | 1156 | ```ts 1157 | import itiririAsync from 'itiriri-async'; 1158 | 1159 | async function* generator() { 1160 | yield* [1, 2, 3]; 1161 | } 1162 | 1163 | (async function () { 1164 | // 5 + 10 + 20 + 30 1165 | await itiririAsync(generator()).reduce((accum, elem) => accum + elem * 10, 5); // returns 65 1166 | })(); 1167 | ``` 1168 | 1169 | ### `skip` 1170 | 1171 | Skips the specified number of elements from the beginning of sequence and returns the remaining ones. 1172 | 1173 | > Syntax 1174 | 1175 | ```ts 1176 | skip(count: number): AsyncIterableQuery; 1177 | ``` 1178 | 1179 | > Parameters 1180 | * `count` - *(required)* number of elements to skip 1181 | 1182 | When *count* is greater than actual number of elements, results in an empty sequence. 1183 | 1184 | > Example 1185 | 1186 | ```ts 1187 | import itiririAsync from 'itiriri-async'; 1188 | 1189 | async function* generator() { 1190 | yield* [1, 2, 3]; 1191 | } 1192 | 1193 | (async function () { 1194 | const q = await itiririAsync(generator()).skip(1).awaitAll(); 1195 | q.toArray(); // returns: [2, 3] 1196 | })(); 1197 | 1198 | (async function () { 1199 | const q = await itiririAsync(generator()).skip(10).awaitAll(); 1200 | q.toArray(); // returns: [] 1201 | })(); 1202 | ``` 1203 | 1204 | `skip` *is a deferred method and is executed only when the result sequence is iterated.* 1205 | 1206 | ### `slice` 1207 | 1208 | Returns a sequence that represents the range of elements from start to end. 1209 | 1210 | > Syntax 1211 | 1212 | ```ts 1213 | slice(start: number, end: number): AsyncIterableQuery; 1214 | ``` 1215 | 1216 | > Parameters 1217 | * `start` - *(required)* zero-based index at which to begin extraction 1218 | * `end` - *(required)* zero-based index before which to end extraction. 1219 | 1220 | The `end` index is not included in the result. 1221 | 1222 | > Example 1223 | 1224 | ```ts 1225 | import itiririAsync from 'itiriri-async'; 1226 | 1227 | async function* generator() { 1228 | yield* [1, 2, 3, 3, 4]; 1229 | } 1230 | 1231 | (async function () { 1232 | const q = await itiririAsync(generator()).slice(2, 4).awaitAll(); 1233 | q.toArray(); // returns: [3, 3] 1234 | })(); 1235 | ``` 1236 | 1237 | `slice` *is a deferred method and is executed only when the result sequence is iterated.* 1238 | 1239 | ### `some` 1240 | 1241 | Tests whether at least one element passes the predicate. 1242 | 1243 | > Syntax 1244 | 1245 | ```ts 1246 | some(predicate: (element: T, index: number) => boolean): Promise; 1247 | ``` 1248 | 1249 | > Parameters 1250 | * `predicate` - *(required)* function to test for each element 1251 | 1252 | > Example 1253 | 1254 | ```ts 1255 | import itiririAsync from 'itiriri-async'; 1256 | 1257 | async function* generator() { 1258 | yield* [1, 2, 3, -3, 4, 0]; 1259 | } 1260 | 1261 | (async function () { 1262 | await itiririAsync(generator()).some(x => x < 0); // returns: true 1263 | await itiririAsync(generator()).some(x => x > 5); // returns: false 1264 | })(); 1265 | ``` 1266 | 1267 | ### `sum` 1268 | 1269 | Returns the sum of all elements. 1270 | 1271 | > Syntax 1272 | 1273 | ```ts 1274 | sum(): number; 1275 | sum(selector: (element: T, index: number) => number): Promise; 1276 | ``` 1277 | 1278 | > Parameters 1279 | * `selector` - *(optional)* a value transformer function to apply to each element 1280 | 1281 | Optionally, a function can be provided to apply a transformation and map each element to a value. 1282 | 1283 | > Example 1284 | 1285 | ```ts 1286 | import itiririAsync from 'itiriri-async'; 1287 | 1288 | async function* generator1() { 1289 | yield* [1, 2, 3]; 1290 | } 1291 | 1292 | async function* generator2() { 1293 | yield* [{ val: 3 }, { val: 5 }]; 1294 | } 1295 | 1296 | (async function () { 1297 | await itiririAsync(generator1()).sum(); // returns: 6 1298 | await itiririAsync(generator2()).sum(x => x.val); // returns: 8 1299 | })(); 1300 | ``` 1301 | 1302 | ### `take` 1303 | 1304 | Returns a specified number of elements from the beginning of sequence. 1305 | 1306 | > Syntax 1307 | 1308 | ```ts 1309 | take(count: number): AsyncIterableQuery; 1310 | ``` 1311 | 1312 | > Parameters 1313 | * `count` - *(required)* number of elements to take 1314 | 1315 | > Example 1316 | 1317 | ```ts 1318 | import itiririAsync from 'itiriri-async'; 1319 | 1320 | async function* generator() { 1321 | yield* [1, 2, 3]; 1322 | } 1323 | 1324 | (async function () { 1325 | const q = await itiririAsync(generator()).take(2).awaitAll(); 1326 | q.toArray(); // returns: [1, 2] 1327 | })(); 1328 | 1329 | (async function () { 1330 | const q = await itiririAsync(generator()).take(0).awaitAll(); 1331 | q.toArray(); // returns: [] 1332 | })(); 1333 | ``` 1334 | 1335 | `take` *is a deferred method and is executed only when the result sequence is iterated.* 1336 | 1337 | ### `union` 1338 | 1339 | Returns a set union with a given sequence. 1340 | 1341 | > Syntax 1342 | 1343 | ```ts 1344 | union(other: AsyncIterable): AsyncIterableQuery; 1345 | union(other: AsyncIterable, selector: (element: T) => S): AsyncIterableQuery; 1346 | ``` 1347 | 1348 | > Parameters 1349 | * `other` - *(required)* the sequence to join with 1350 | * `selector` - *(optional)* a value transformer function to be used for comparisons 1351 | 1352 | Example 1353 | 1354 | ```ts 1355 | import itiririAsync from 'itiriri-async'; 1356 | 1357 | async function* generator1() { 1358 | yield* [1, 2, 3]; 1359 | } 1360 | 1361 | async function* generator2() { 1362 | yield* [3, 4, 5]; 1363 | } 1364 | 1365 | (async function () { 1366 | const q = await itiririAsync(generator1()).union(generator2()).awaitAll(); 1367 | q.toArray(); // returns [1, 2, 3, 4, 5] 1368 | })(); 1369 | ``` 1370 | 1371 | `union` *is a deferred method and is executed only when the result sequence is iterated.* 1372 | 1373 | ### `values` 1374 | 1375 | Returns a sequence of values for each index in the source sequence. 1376 | 1377 | > Syntax 1378 | 1379 | ```ts 1380 | values(): AsyncIterableQuery; 1381 | ``` 1382 | 1383 | > Example 1384 | 1385 | ```ts 1386 | import itiririAsync from 'itiriri-async'; 1387 | 1388 | async function* generator() { 1389 | yield* [1, 0, 2, 3]; 1390 | } 1391 | 1392 | (async function () { 1393 | const q = await itiririAsync(generator()).values().awaitAll(); 1394 | q.toArray(); // [1, 0, 2, 3] 1395 | })(); 1396 | ``` 1397 | 1398 | `values` *is a deferred method and is executed only when the result sequence is iterated.* 1399 | 1400 | ## License 1401 | 1402 | [MIT](LICENSE) -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /examples/todos.ts: -------------------------------------------------------------------------------- 1 | import * as WebRequest from 'web-request'; 2 | import { default as itiririAsync } from '../lib'; 3 | 4 | type ToDo = { 5 | userId: number, 6 | id: number, 7 | title: string, 8 | completed: boolean, 9 | }; 10 | 11 | async function* todosAsync(): AsyncIterableIterator { 12 | let id = 1; 13 | while (true) { 14 | yield await WebRequest.json(`https://jsonplaceholder.typicode.com/todos/${id++}`); 15 | } 16 | } 17 | 18 | async function showTop2ToDos(): Promise { 19 | const todos = await itiririAsync(todosAsync()) 20 | .filter(x => !x.completed) 21 | .take(2) 22 | .awaitAll(); 23 | 24 | console.log(todos.toArray()); 25 | } 26 | 27 | console.log('Next 2 TODOs:'); 28 | showTop2ToDos(); 29 | 30 | /* 31 | [ { userId: 1, 32 | id: 1, 33 | title: 'delectus aut autem', 34 | completed: false }, 35 | { userId: 1, 36 | id: 2, 37 | title: 'quis ut nam facilis et officia qui', 38 | completed: false }] 39 | */ 40 | 41 | async function showCompletedToDos() { 42 | const completed = itiririAsync(todosAsync()) 43 | .filter(e => e.completed === true); 44 | 45 | for await (const todo of completed) { 46 | console.log(todo); 47 | } 48 | } 49 | 50 | console.log('All completed TODOs:'); 51 | showCompletedToDos(); 52 | 53 | /* 54 | { userId: 1, id: 4, title: 'et porro tempora', completed: true } 55 | { userId: 1, 56 | id: 8, 57 | title: 'quo adipisci enim quam ut ab', 58 | completed: true } 59 | { userId: 1, 60 | id: 10, 61 | title: 'illo est ratione doloremque quia maiores aut', 62 | completed: true } 63 | { userId: 1, 64 | id: 11, 65 | title: 'vero rerum temporibus dolor', 66 | completed: true } 67 | ... 68 | ... 69 | ... 70 | */ 71 | -------------------------------------------------------------------------------- /gulpfile.ts: -------------------------------------------------------------------------------- 1 | import * as browserify from 'browserify'; 2 | import * as del from 'del'; 3 | import * as gulp from 'gulp'; 4 | import * as mocha from 'gulp-mocha'; 5 | import * as replace from 'gulp-replace'; 6 | import * as shell from 'gulp-shell'; 7 | import * as sourcemaps from 'gulp-sourcemaps'; 8 | import { default as tslint } from 'gulp-tslint'; 9 | import * as ts from 'gulp-typescript'; 10 | import { default as uglify } from 'gulp-uglify-es'; 11 | import { Gulpclass, MergedTask, SequenceTask, Task } from 'gulpclass'; 12 | import * as buffer from 'vinyl-buffer'; 13 | import * as source from 'vinyl-source-stream'; 14 | 15 | @Gulpclass() 16 | export class Gulpfile { 17 | 18 | /** 19 | * Cleans build folder. 20 | */ 21 | @Task() 22 | clean() { 23 | return del(['./build/**', './coverage/**']); 24 | } 25 | 26 | /** 27 | * Runs typescript files compilation. 28 | */ 29 | @Task() 30 | compile() { 31 | return gulp.src('./package.json', { read: false }) 32 | .pipe(shell(['tsc'])); 33 | } 34 | 35 | /** 36 | * Runs unit-tests. 37 | */ 38 | @Task() 39 | unit() { 40 | // back compatibility 41 | // node < v10 requires --harmony_async_iteration for async iterators 42 | if (getProcessVersion() < 10) { 43 | return gulp.src('./build/compiled/test/**/*.js') 44 | .pipe(mocha({ harmony_async_iteration: '', harmony_promise_finally: '' })); 45 | } 46 | return gulp.src('./build/compiled/test/**/*.js').pipe(mocha()); 47 | } 48 | 49 | /** 50 | * Compiles the code and runs tests. 51 | */ 52 | @SequenceTask() 53 | test() { 54 | return ['clean', 'compile', 'unit']; 55 | } 56 | 57 | /** 58 | * Runs the tslint. 59 | */ 60 | @Task() 61 | tslint() { 62 | return gulp.src(['./lib/**/*.ts', './test/**/*.ts', './examples/**/*.ts']) 63 | .pipe(tslint({ formatter: 'stylish' })) 64 | .pipe(tslint.report({ 65 | emitError: true, 66 | summarizeFailureOutput: true, 67 | })); 68 | } 69 | 70 | /** 71 | * Copies all sources to the package directory. 72 | */ 73 | @MergedTask() 74 | packageCompile() { 75 | const tsProject = ts.createProject('tsconfig.json'); 76 | const tsResult = gulp.src(['lib/**/*.ts']) 77 | .pipe(sourcemaps.init()) 78 | .pipe(tsProject()); 79 | 80 | return [ 81 | tsResult.dts.pipe(gulp.dest('build/package')), 82 | tsResult.js 83 | .pipe(sourcemaps.write('.', { sourceRoot: '', includeContent: true })) 84 | .pipe(gulp.dest('build/package')), 85 | ]; 86 | } 87 | 88 | /** 89 | * Moves all compiled files to the final package directory. 90 | */ 91 | @Task() 92 | packageMoveCompiledFiles() { 93 | return gulp.src('./build/package/lib/**/*') 94 | .pipe(gulp.dest('./build/package')); 95 | } 96 | 97 | /** 98 | * Clears the directory with compiled files. 99 | */ 100 | @Task() 101 | packageClearCompileDirectory() { 102 | return del(['build/package/lib/**']); 103 | } 104 | 105 | /** 106 | * Change the "private" state of the packaged package.json file to public. 107 | */ 108 | @Task() 109 | packagePreparePackageFile() { 110 | return gulp.src('./package.json') 111 | .pipe(replace('\"private\": true,', '\"private\": false,')) 112 | .pipe(gulp.dest('./build/package')); 113 | } 114 | 115 | /** 116 | * This task will replace all typescript code blocks in the README 117 | * (since npm does not support typescript syntax highlighting) 118 | * and copy this README file into the package folder. 119 | */ 120 | @Task() 121 | packageReadmeFile() { 122 | return gulp.src('./README.md') 123 | .pipe(replace(/```ts([\s\S]*?)```/g, '```javascript$1```')) 124 | .pipe(gulp.dest('./build/package')); 125 | } 126 | 127 | /** 128 | * Creates a package that can be published to npm. 129 | */ 130 | @SequenceTask() 131 | package() { 132 | return [ 133 | 'clean', 134 | 'packageCompile', 135 | 'packageMoveCompiledFiles', 136 | 'packageClearCompileDirectory', 137 | ['packagePreparePackageFile', 'packageReadmeFile'], 138 | ]; 139 | } 140 | 141 | /** 142 | * Publishes a package to npm from ./build/package directory. 143 | */ 144 | @Task() 145 | npmPublish() { 146 | return gulp.src('./package.json', { read: false }) 147 | .pipe(shell(['cd ./build/package && npm publish --access public'])); 148 | } 149 | 150 | /** 151 | * Creates a package and publishes it to npm. 152 | */ 153 | @SequenceTask() 154 | publish() { 155 | return ['test', 'tslint', 'package', 'npmPublish']; 156 | } 157 | 158 | @Task() 159 | bundle() { 160 | return browserify({ 161 | standalone: 'itiririAsync', 162 | entries: './build/compiled/lib/index.js', 163 | }).bundle() 164 | .on('error', e => console.error(e)) 165 | .pipe(source('itiriri-async.min.js')) 166 | .pipe(buffer()) 167 | .pipe(uglify()) 168 | .pipe(gulp.dest('.')); 169 | } 170 | 171 | @Task() 172 | coverage() { 173 | const flags = getProcessVersion() < 10 174 | ? '--harmony_async_iteration --harmony-promise-finally' : ''; 175 | 176 | return gulp.src('./package.json', { read: false }) 177 | .pipe(shell([ 178 | `node ${flags} node_modules/.bin/istanbul cover ` + 179 | 'node_modules/mocha/bin/_mocha ' + 180 | './build/compiled/test ./build/compiled/lib -- --recursive'])); 181 | } 182 | } 183 | 184 | function getProcessVersion(defaultVersion: number = 10) { 185 | if (process && process.version) { 186 | const tokens = process.version.match(/^v(\d+\.\d+)/); 187 | return tokens ? Number.parseInt(tokens[1], 10) : defaultVersion; 188 | } 189 | 190 | return defaultVersion; 191 | } 192 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | import { IterableQuery } from 'itiriri'; 2 | import { isIterable } from 'itiriri/utils/isIterable'; 3 | import { concat } from './iterators/concat'; 4 | import { distinct } from './iterators/distinct'; 5 | import { exclude } from './iterators/exclude'; 6 | import { filter } from './iterators/filter'; 7 | import { flat } from './iterators/flat'; 8 | import { groupJoin } from './iterators/groupJoin'; 9 | import { intersect } from './iterators/intersect'; 10 | import { join } from './iterators/join'; 11 | import { leftJoin } from './iterators/leftJoin'; 12 | import { map } from './iterators/map'; 13 | import { skip } from './iterators/skip'; 14 | import { slice } from './iterators/slice'; 15 | import { take } from './iterators/take'; 16 | import { average } from './reducers/average'; 17 | import { awaitAll } from './reducers/awaitAll'; 18 | import { first } from './reducers/first'; 19 | import { forEach } from './reducers/forEach'; 20 | import { indexOf } from './reducers/indexOf'; 21 | import { last } from './reducers/last'; 22 | import { lastIndexOf } from './reducers/lastIndexOf'; 23 | import { length } from './reducers/length'; 24 | import { max } from './reducers/max'; 25 | import { min } from './reducers/min'; 26 | import { nth } from './reducers/nth'; 27 | import { reduce } from './reducers/reduce'; 28 | import { sum } from './reducers/sum'; 29 | import { AsyncIterableQuery } from './types/AsyncIterableQuery'; 30 | import { asyncIterator } from './utils/asyncIterator'; 31 | import { isAsyncIterable } from './utils/isAsyncIterable'; 32 | 33 | /** 34 | * Creates a queryable iterable. 35 | * @param source an async iterable that implements the `Symbol.asyncIterator` method. 36 | */ 37 | export default function itiririAsync(source: AsyncIterable): AsyncIterableQuery { 38 | return new ItiririAsync(source); 39 | } 40 | 41 | export { AsyncIterableQuery }; 42 | 43 | class ItiririAsync implements AsyncIterableQuery{ 44 | constructor(private readonly source: AsyncIterable) { 45 | } 46 | 47 | [Symbol.asyncIterator](): AsyncIterator { 48 | return asyncIterator(this.source); 49 | } 50 | 51 | // #region common methods 52 | entries(): AsyncIterableQuery<[number, T]> { 53 | return new ItiririAsync( 54 | map(this.source, (elem, idx) => <[number, T]>[idx, elem]), 55 | ); 56 | } 57 | 58 | keys(): AsyncIterableQuery { 59 | return new ItiririAsync(map(this.source, (_, idx) => idx)); 60 | } 61 | 62 | values(): AsyncIterableQuery { 63 | return new ItiririAsync(this.source); 64 | } 65 | 66 | forEach(action: (element: T, index: number) => void): Promise { 67 | return forEach(this.source, action); 68 | } 69 | 70 | concat(other: T | Promise | Iterable | AsyncIterable): AsyncIterableQuery { 71 | if (isIterable(other)) { 72 | return new ItiririAsync(concat( 73 | this.source, 74 | (async function* (e) { yield* e; })(other), 75 | )); 76 | } 77 | return isAsyncIterable(other) ? 78 | new ItiririAsync(concat(this.source, other)) : 79 | new ItiririAsync(concat(this.source, (async function* (e) { yield e; })(other))); 80 | } 81 | 82 | prepend(other: T | Promise | Iterable | AsyncIterable): AsyncIterableQuery { 83 | if (isIterable(other)) { 84 | return new ItiririAsync(concat( 85 | (async function* (e) { yield* e; })(other), 86 | this.source, 87 | )); 88 | } 89 | return isAsyncIterable(other) ? 90 | new ItiririAsync(concat(other, this.source)) : 91 | new ItiririAsync(concat( 92 | (async function* (e) { yield await e; })(other), 93 | this.source, 94 | )); 95 | } 96 | // #endregion 97 | 98 | // #region IterableValue implementation 99 | nth(index: number): Promise { 100 | return nth(this.source, index); 101 | } 102 | 103 | indexOf(element: T, fromIndex: number = 0): Promise { 104 | return indexOf(this.source, (elem, idx) => idx >= fromIndex && elem === element); 105 | } 106 | 107 | findIndex(predicate: (element: T, index: number) => boolean): Promise { 108 | return indexOf(this.source, predicate); 109 | } 110 | 111 | lastIndexOf(element: T, fromIndex: number = 0): Promise { 112 | return lastIndexOf(this.source, (elem, idx) => idx >= fromIndex && elem === element); 113 | } 114 | 115 | findLastIndex(predicate: (element: T, index: number) => boolean): Promise { 116 | return lastIndexOf(this.source, predicate); 117 | } 118 | 119 | length(predicate: (element: T, index: number) => boolean = alwaysTrue()): Promise { 120 | return length(filter(this.source, predicate)); 121 | } 122 | 123 | first(): Promise { 124 | return first(this.source); 125 | } 126 | 127 | find(predicate: (element: T, index: number) => boolean): Promise { 128 | return first(filter(this.source, predicate)); 129 | } 130 | 131 | last(): Promise { 132 | return last(this.source); 133 | } 134 | 135 | findLast(predicate: (element: T, index: number) => boolean): Promise { 136 | return last(filter(this.source, predicate)); 137 | } 138 | 139 | average( 140 | selector: (element: T, index: number) => number = element(), 141 | ): Promise { 142 | 143 | return average(map(this.source, selector)); 144 | } 145 | 146 | min(compareFn: (element1: T, element2: T) => number = comparer()): Promise { 147 | return min(this.source, compareFn); 148 | } 149 | 150 | max(compareFn: (element1: T, element2: T) => number = comparer()): Promise { 151 | return max(this.source, compareFn); 152 | } 153 | 154 | sum( 155 | selector: (element: T, index: number) => number = element(), 156 | ): Promise { 157 | return sum(map(this.source, selector)); 158 | } 159 | 160 | awaitAll(): Promise> { 161 | return awaitAll(this.source); 162 | } 163 | 164 | reduce( 165 | callback: (accumulator: TResult | T, current: T, index: number) => any, 166 | initialValue?: any, 167 | ): Promise { 168 | return reduce(this.source, callback, initialValue); 169 | } 170 | 171 | // #region IterablePredicate implementation 172 | every(predicate: (element: T, index: number) => boolean): Promise { 173 | return indexOf(this.source, (e, i) => !predicate(e, i)).then(index => index === -1); 174 | } 175 | 176 | some(predicate: (element: T, index: number) => boolean): Promise { 177 | return indexOf(this, predicate).then(index => index !== -1); 178 | } 179 | 180 | includes(element: T, fromIndex: number = 0): Promise { 181 | return this.some((elem, idx) => idx >= fromIndex && elem === element); 182 | } 183 | // #endregion 184 | 185 | // #region IterableFilter implementation 186 | filter(predicate: (element: T, index: number) => boolean): AsyncIterableQuery { 187 | return new ItiririAsync(filter(this.source, predicate)); 188 | } 189 | 190 | take(count: number): AsyncIterableQuery { 191 | return new ItiririAsync(take(this.source, count)); 192 | } 193 | 194 | skip(count: number): AsyncIterableQuery { 195 | return new ItiririAsync(skip(this.source, count)); 196 | } 197 | 198 | slice(start?: number, end?: number): AsyncIterableQuery { 199 | return new ItiririAsync(slice(this, start, end)); 200 | } 201 | // #endregion 202 | 203 | // #region IterableTransformation implementation 204 | map(selector: (element: T, index: number) => S): AsyncIterableQuery { 205 | return new ItiririAsync(map(this.source, selector)); 206 | } 207 | 208 | flat( 209 | selector: (element: T, index: number) => AsyncIterable = element(), 210 | ): AsyncIterableQuery { 211 | return new ItiririAsync(flat(this.map(selector))); 212 | } 213 | 214 | // #endregion 215 | 216 | // #region IterableSet implementation 217 | distinct(selector: (element: T) => S = element()): AsyncIterableQuery { 218 | return new ItiririAsync(distinct(this.source, selector)); 219 | } 220 | 221 | exclude( 222 | other: Iterable, 223 | selector: (element: T) => S = element(), 224 | ): AsyncIterableQuery { 225 | return new ItiririAsync(exclude(this.source, other, selector)); 226 | } 227 | 228 | intersect( 229 | other: Iterable, 230 | selector: (element: T) => S = element(), 231 | ): AsyncIterableQuery { 232 | return new ItiririAsync(intersect(this.source, other, selector)); 233 | } 234 | 235 | union( 236 | other: AsyncIterable, 237 | selector: (element: T) => S = element(), 238 | ): AsyncIterableQuery { 239 | return new ItiririAsync(distinct(concat(this.source, other), selector)); 240 | } 241 | // #endregion 242 | 243 | // #region IterableJoin implementation 244 | join( 245 | other: Iterable, 246 | leftKeySelector: (element: T, index: number) => TKey, 247 | rightKeySelector: (element: TRight, index: number) => TKey, 248 | joinSelector: (left: T, right: TRight) => TResult, 249 | ): AsyncIterableQuery { 250 | const iterator = join( 251 | this.source, 252 | other, 253 | leftKeySelector, 254 | rightKeySelector, 255 | joinSelector); 256 | 257 | return new ItiririAsync(iterator); 258 | } 259 | 260 | leftJoin( 261 | other: Iterable, 262 | leftKeySelector: (element: T, index: number) => TKey, 263 | rightKeySelector: (element: TRight, index: number) => TKey, 264 | joinSelector: (left: T, right?: TRight) => TResult, 265 | ): AsyncIterableQuery { 266 | const iterator = leftJoin( 267 | this.source, 268 | other, 269 | leftKeySelector, 270 | rightKeySelector, 271 | joinSelector); 272 | 273 | return new ItiririAsync(iterator); 274 | } 275 | 276 | groupJoin( 277 | other: Iterable, 278 | leftKeySelector: (element: T, index: number) => TKey, 279 | rightKeySelector: (element: TRight, index: number) => TKey, 280 | joinSelector: (left: T, right: TRight[]) => TResult, 281 | ): AsyncIterableQuery { 282 | const iterator = groupJoin( 283 | this.source, 284 | other, 285 | leftKeySelector, 286 | rightKeySelector, 287 | joinSelector); 288 | 289 | return new ItiririAsync(iterator); 290 | } 291 | // #endregion 292 | } 293 | 294 | function element() { 295 | return (e: any) => e; 296 | } 297 | 298 | function alwaysTrue() { 299 | return () => true; 300 | } 301 | 302 | function comparer() { 303 | return (a: T, b: T) => a === b ? 0 : (a > b ? 1 : -1); 304 | } 305 | -------------------------------------------------------------------------------- /lib/iterators/concat.ts: -------------------------------------------------------------------------------- 1 | import { asyncIterable } from '../utils/asyncIterable'; 2 | 3 | export function concat( 4 | left: AsyncIterable, 5 | rigth: AsyncIterable, 6 | ): AsyncIterable { 7 | return asyncIterable(async function* () { 8 | yield* await left; 9 | yield* await rigth; 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /lib/iterators/distinct.ts: -------------------------------------------------------------------------------- 1 | import { asyncIterable } from '../utils/asyncIterable'; 2 | 3 | export function distinct( 4 | source: AsyncIterable, 5 | keySelector: (element: TElement) => TKey, 6 | ): AsyncIterable { 7 | return asyncIterable(async function* () { 8 | const set = new Set(); 9 | 10 | for await (const element of source) { 11 | const key = keySelector(element); 12 | 13 | if (!set.has(key)) { 14 | set.add(key); 15 | yield await element; 16 | } 17 | } 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /lib/iterators/exclude.ts: -------------------------------------------------------------------------------- 1 | import { asyncIterable } from '../utils/asyncIterable'; 2 | import { map } from 'itiriri/iterators/map'; 3 | import { toSet } from 'itiriri/reducers/toSet'; 4 | 5 | export function exclude( 6 | source: AsyncIterable, 7 | exclude: Iterable, 8 | keySelector: (element: TElement) => TKey, 9 | ): AsyncIterable { 10 | return asyncIterable(async function* () { 11 | const exclusionSet = toSet(map(exclude, keySelector)); 12 | 13 | for await (const element of source) { 14 | const key = keySelector(element); 15 | 16 | if (!exclusionSet.has(key)) { 17 | yield await element; 18 | } 19 | } 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /lib/iterators/filter.ts: -------------------------------------------------------------------------------- 1 | import { asyncIterable } from '../utils/asyncIterable'; 2 | 3 | export function filter( 4 | source: AsyncIterable, 5 | predicate: (element: TElement, index: number) => boolean, 6 | ): AsyncIterable { 7 | return asyncIterable(async function* () { 8 | let index = 0; 9 | 10 | for await (const element of source) { 11 | if (predicate(element, index++)) { 12 | yield await element; 13 | } 14 | } 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /lib/iterators/flat.ts: -------------------------------------------------------------------------------- 1 | import { asyncIterable } from '../utils/asyncIterable'; 2 | 3 | export function flat(iterables: AsyncIterable>): AsyncIterable; 4 | export function flat(iterables: AsyncIterable>): AsyncIterable; 5 | export function flat(iterables: Iterable>): AsyncIterable; 6 | export function flat( 7 | iterables: Iterable> | 8 | AsyncIterable> | 9 | AsyncIterable>, 10 | ): AsyncIterable { 11 | return asyncIterable(async function* () { 12 | for await (const element of iterables) { 13 | yield* element; 14 | } 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /lib/iterators/groupJoin.ts: -------------------------------------------------------------------------------- 1 | import { asyncIterable } from '../utils/asyncIterable'; 2 | import { toGroups } from 'itiriri/reducers/toGroups'; 3 | 4 | export function groupJoin( 5 | source: AsyncIterable, 6 | others: Iterable, 7 | leftKeySelector: (element: TLeft, index: number) => TKey, 8 | rightKeySelector: (element: TRight, index: number) => TKey, 9 | joinSelector: (left: TLeft, right: TRight[]) => TResult, 10 | ): AsyncIterable { 11 | return asyncIterable(async function* () { 12 | let index = 0; 13 | const rightMap = toGroups(others, rightKeySelector, x => x); 14 | 15 | for await (const element of source) { 16 | const leftKey = leftKeySelector(element, index++); 17 | const rightValues = rightMap.get(leftKey) || []; 18 | 19 | yield await joinSelector(element, rightValues); 20 | } 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /lib/iterators/intersect.ts: -------------------------------------------------------------------------------- 1 | import { asyncIterable } from '../utils/asyncIterable'; 2 | import { map } from 'itiriri/iterators/map'; 3 | import { toSet } from 'itiriri/reducers/toSet'; 4 | 5 | export function intersect( 6 | source: AsyncIterable, 7 | others: Iterable, 8 | selector: (element: TElement) => TKey, 9 | ): AsyncIterable { 10 | return asyncIterable(async function* () { 11 | const includedSet = new Set(); 12 | const othersSet = toSet(map(others, selector)); 13 | 14 | for await (const element of source) { 15 | const key = selector(element); 16 | 17 | if (!includedSet.has(key) && othersSet.has(key)) { 18 | includedSet.add(key); 19 | yield await element; 20 | } 21 | } 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /lib/iterators/join.ts: -------------------------------------------------------------------------------- 1 | import { asyncIterable } from '../utils/asyncIterable'; 2 | import { toGroups } from 'itiriri/reducers/toGroups'; 3 | 4 | export function join( 5 | source: AsyncIterable, 6 | others: Iterable, 7 | leftKeySelector: (element: TLeft, index: number) => TKey, 8 | rightKeySelector: (element: TRight, index: number) => TKey, 9 | joinSelector: (left: TLeft, right: TRight) => TResult, 10 | ): AsyncIterable { 11 | return asyncIterable(async function* () { 12 | let index = 0; 13 | const rightMap = toGroups(others, rightKeySelector, x => x); 14 | 15 | for await (const element of source) { 16 | const leftKey = leftKeySelector(element, index++); 17 | const rightValues = rightMap.get(leftKey); 18 | 19 | if (rightValues) { 20 | for (const rightMatch of rightValues) { 21 | yield await joinSelector(element, rightMatch); 22 | } 23 | } 24 | 25 | } 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /lib/iterators/leftJoin.ts: -------------------------------------------------------------------------------- 1 | import { asyncIterable } from '../utils/asyncIterable'; 2 | import { toGroups } from 'itiriri/reducers/toGroups'; 3 | 4 | export function leftJoin( 5 | source: AsyncIterable, 6 | others: Iterable, 7 | leftKeySelector: (element: TLeft, index: number) => TKey, 8 | rightKeySelector: (element: TRight, index: number) => TKey, 9 | joinSelector: (left: TLeft, right?: TRight) => TResult, 10 | ): AsyncIterable { 11 | return asyncIterable(async function* () { 12 | let index = 0; 13 | const rightMap = toGroups(others, rightKeySelector, x => x); 14 | 15 | for await (const element of source) { 16 | const leftKey = leftKeySelector(element, index++); 17 | const rightValues = rightMap.get(leftKey); 18 | 19 | if (rightValues) { 20 | for (const rightMatch of rightValues) { 21 | yield await joinSelector(element, rightMatch); 22 | } 23 | } else { 24 | yield await joinSelector(element); 25 | } 26 | 27 | } 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /lib/iterators/map.ts: -------------------------------------------------------------------------------- 1 | import { asyncIterable } from '../utils/asyncIterable'; 2 | 3 | export function map( 4 | source: AsyncIterable, 5 | transform: (element: TElement, index: number) => TResult, 6 | ): AsyncIterable { 7 | return asyncIterable(async function* () { 8 | let index = 0; 9 | 10 | for await (const element of source) { 11 | yield await transform(element, index++); 12 | } 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /lib/iterators/skip.ts: -------------------------------------------------------------------------------- 1 | import { asyncIterable } from '../utils/asyncIterable'; 2 | import { filter } from './filter'; 3 | 4 | export function skip( 5 | source: AsyncIterable, 6 | count: number, 7 | ): AsyncIterable { 8 | if (count < 0) { 9 | throw Error('Negative count is not supported, use await and sync iterator instead.'); 10 | } 11 | 12 | return asyncIterable(async function* () { 13 | yield* filter(source, (_, index) => index >= count); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /lib/iterators/slice.ts: -------------------------------------------------------------------------------- 1 | import { take } from './take'; 2 | import { skip } from './skip'; 3 | 4 | export function slice( 5 | source: AsyncIterable, 6 | start?: number, 7 | end?: number, 8 | ): AsyncIterable { 9 | if (start === undefined && end === undefined) { 10 | return source; 11 | } 12 | 13 | if (start !== undefined && end === undefined) { 14 | return skip(source, check(start, 'start')); 15 | } 16 | 17 | if (start === undefined && end !== undefined) { 18 | return take(source, check(end, 'end')); 19 | } 20 | 21 | if (start !== undefined && start < 0) { 22 | throw new Error('Invalid start range, use positive index.'); 23 | } 24 | 25 | // start !== undefined && end !== undefined 26 | return skip(take(source, check(end, 'end')), check(start, 'start')); 27 | } 28 | 29 | function check(value: number, name: string) { 30 | if (value < 0) { 31 | throw new Error(`Invalid ${name} range, use positive index.`); 32 | } 33 | 34 | return value; 35 | } 36 | -------------------------------------------------------------------------------- /lib/iterators/take.ts: -------------------------------------------------------------------------------- 1 | import { asyncIterable } from '../utils/asyncIterable'; 2 | 3 | export function take( 4 | source: AsyncIterable, 5 | count: number, 6 | ): AsyncIterable { 7 | if (count < 0) { 8 | throw Error('Negative count is not supported, use await and sync iterator instead.'); 9 | } 10 | 11 | return asyncIterable(async function* () { 12 | let n = count; 13 | 14 | for await (const element of source) { 15 | if (n-- === 0) return; 16 | 17 | yield await element; 18 | } 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /lib/reducers/average.ts: -------------------------------------------------------------------------------- 1 | export async function average(source: AsyncIterable): Promise { 2 | let [s, n] = [0, 0]; 3 | 4 | for await (const element of source) { 5 | [s, n] = [s + element, n + 1]; 6 | } 7 | 8 | return n > 0 ? s / n : undefined; 9 | } 10 | -------------------------------------------------------------------------------- /lib/reducers/awaitAll.ts: -------------------------------------------------------------------------------- 1 | import { default as itiriri, IterableQuery } from 'itiriri'; 2 | 3 | export async function awaitAll( 4 | source: AsyncIterable, 5 | ): Promise> { 6 | 7 | const buffer: TElement[] = []; 8 | for await (const element of source) { 9 | buffer.push(element); 10 | } 11 | 12 | return itiriri(buffer); 13 | } 14 | -------------------------------------------------------------------------------- /lib/reducers/first.ts: -------------------------------------------------------------------------------- 1 | export async function first(source: AsyncIterable): 2 | Promise { 3 | 4 | for await (const element of source) { 5 | return Promise.resolve(element); 6 | } 7 | 8 | return undefined; 9 | } 10 | -------------------------------------------------------------------------------- /lib/reducers/forEach.ts: -------------------------------------------------------------------------------- 1 | export async function forEach( 2 | source: AsyncIterable, 3 | action: (element: T, index: number) => void, 4 | ): Promise { 5 | let index = 0; 6 | for await (const element of source) { 7 | action(element, index++); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/reducers/indexOf.ts: -------------------------------------------------------------------------------- 1 | export async function indexOf( 2 | source: AsyncIterable, 3 | predicate: (element: TElement, index: number) => boolean, 4 | ): Promise { 5 | let index = -1; 6 | 7 | for await (const element of source) { 8 | if (predicate(element, ++index)) { 9 | return index; 10 | } 11 | } 12 | 13 | return -1; 14 | } 15 | -------------------------------------------------------------------------------- /lib/reducers/last.ts: -------------------------------------------------------------------------------- 1 | export async function last(source: AsyncIterable): 2 | Promise { 3 | 4 | let value: TElement | undefined = undefined; 5 | 6 | for await (const element of source) { 7 | value = element; 8 | } 9 | 10 | return value; 11 | } 12 | -------------------------------------------------------------------------------- /lib/reducers/lastIndexOf.ts: -------------------------------------------------------------------------------- 1 | export async function lastIndexOf( 2 | source: AsyncIterable, 3 | predicate: (element: TElement, index: number) => boolean, 4 | ): Promise { 5 | let [result, index] = [-1, -1]; 6 | 7 | for await (const element of source) { 8 | if (predicate(element, ++index)) { 9 | result = index; 10 | } 11 | } 12 | 13 | return result; 14 | } 15 | -------------------------------------------------------------------------------- /lib/reducers/length.ts: -------------------------------------------------------------------------------- 1 | export async function length(source: AsyncIterable): Promise { 2 | let cnt = 0; 3 | 4 | for await (const _ of source) { 5 | cnt++; 6 | } 7 | 8 | return cnt; 9 | } 10 | -------------------------------------------------------------------------------- /lib/reducers/max.ts: -------------------------------------------------------------------------------- 1 | export async function max( 2 | source: AsyncIterable, 3 | compareFn: (element1: TElement, element2: TElement) => number, 4 | ): Promise { 5 | let result: TElement | undefined = undefined; 6 | 7 | for await (const element of source) { 8 | if (result === undefined || compareFn(element, result) > 0) { 9 | result = element; 10 | } 11 | } 12 | 13 | return result; 14 | } 15 | -------------------------------------------------------------------------------- /lib/reducers/min.ts: -------------------------------------------------------------------------------- 1 | export async function min( 2 | source: AsyncIterable, 3 | compareFn: (element1: TElement, element2: TElement) => number, 4 | ): Promise { 5 | let result: TElement | undefined = undefined; 6 | 7 | for await (const element of source) { 8 | if (result === undefined || compareFn(element, result) < 0) { 9 | result = element; 10 | } 11 | } 12 | 13 | return result; 14 | } 15 | -------------------------------------------------------------------------------- /lib/reducers/nth.ts: -------------------------------------------------------------------------------- 1 | export async function nth( 2 | source: AsyncIterable, 3 | index: number, 4 | ): Promise { 5 | if (index < 0) { 6 | throw Error('Negative index is not supported, use await and sync iterator instead.'); 7 | } 8 | 9 | let n = index; 10 | 11 | for await (const element of source) { 12 | if (!n--) { 13 | return element; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/reducers/reduce.ts: -------------------------------------------------------------------------------- 1 | export async function reduce( 2 | source: AsyncIterable, 3 | callback: (accumulator: TElement, current: TElement, index: number) => TElement, 4 | ): Promise; 5 | 6 | export async function reduce( 7 | source: AsyncIterable, 8 | callback: (accumulator: TAccumulator, current: TElement, index: number) => TAccumulator, 9 | initialValue: TAccumulator, 10 | ): Promise; 11 | 12 | export async function reduce( 13 | source: AsyncIterable, 14 | callback: (accumulator: any, current: TElement, index: number) => any, 15 | initialValue?: any, 16 | ): Promise { 17 | let [index, accumulator] = [-1, initialValue]; 18 | 19 | for await (const element of source) { 20 | accumulator = ++index === 0 && initialValue === undefined ? 21 | element : 22 | callback(accumulator, element, index); 23 | } 24 | 25 | if (initialValue === undefined && index === -1) { 26 | throw new Error('Sequence contains no elements.'); 27 | } 28 | 29 | return accumulator; 30 | } 31 | -------------------------------------------------------------------------------- /lib/reducers/sum.ts: -------------------------------------------------------------------------------- 1 | export async function sum(source: AsyncIterable): Promise { 2 | let [result, hasElements] = [0, false]; 3 | 4 | for await (const element of source) { 5 | [result, hasElements] = [result + element, true]; 6 | } 7 | 8 | return hasElements ? result : undefined; 9 | } 10 | -------------------------------------------------------------------------------- /lib/types/AsyncIterableFilter.ts: -------------------------------------------------------------------------------- 1 | import { AsyncIterableQuery } from './AsyncIterableQuery'; 2 | 3 | /** 4 | * Apply a filter over an iterable and return a new iterable. 5 | */ 6 | export interface AsyncIterableFilter extends AsyncIterable { 7 | /** 8 | * Returns a sequence of elements that pass the predicate. 9 | * @param {(element:T,index:number)=>boolean} predicate element predicate 10 | * @returns AsyncIterableQuery 11 | */ 12 | filter(predicate: (element: T, index: number) => boolean): AsyncIterableQuery; 13 | 14 | /** 15 | * Returns a specified number of elements from the beginning of sequence. 16 | * If a negative count is specified, returns elements from the end of the sequence. 17 | * @param {number} count 18 | * @returns AsyncIterable 19 | */ 20 | take(count: number): AsyncIterableQuery; 21 | 22 | /** 23 | * Skips the specified number of elements from the beggining of sequence 24 | * and returns the remaining ones. 25 | * If a negative count is specified, skips elements from the end of the sequence. 26 | * @param {number} count 27 | * @returns AsyncIterable 28 | */ 29 | skip(count: number): AsyncIterableQuery; 30 | 31 | /** 32 | * Returns a sequence that represents the range of elements from start to end. 33 | * @returns AsyncIterable 34 | */ 35 | slice(): AsyncIterableQuery; 36 | 37 | /** 38 | * Returns a sequence that represents the range of elements from start to end. 39 | * @param start zero-based index at which to start extraction 40 | * @returns AsyncIterable 41 | */ 42 | slice(start: number): AsyncIterableQuery; 43 | 44 | /** 45 | * Returns a sequence that represents the range of elements from start to end. 46 | * @param start zero-based index at which to start extraction 47 | * @param end zero-based index before which to end extraction (not including) 48 | * @returns AsyncIterable 49 | */ 50 | slice(start: number, end: number): AsyncIterableQuery; 51 | } 52 | -------------------------------------------------------------------------------- /lib/types/AsyncIterableJoin.ts: -------------------------------------------------------------------------------- 1 | import { AsyncIterableQuery } from './AsyncIterableQuery'; 2 | 3 | /** 4 | * Join two iterables to produce a new iterable. 5 | */ 6 | export interface AsyncIterableJoin extends AsyncIterable { 7 | /** 8 | * Returns a sequence of correlated elements tranformation that match a given key. 9 | * @param {Iterable} other 10 | * @param {(element:T,index:number)=>TKey} leftKeySelector left element key selector 11 | * @param {(element:TRight,index:number)=>TKey} rightKeySelector right element key selector 12 | * @param {(left:T,right:TRight)=>TResult} joinSelector transformation 13 | * @returns AsyncIterable 14 | */ 15 | join( 16 | other: Iterable, 17 | leftKeySelector: (element: T, index: number) => TKey, 18 | rightKeySelector: (element: TRight, index: number) => TKey, 19 | joinSelector: (left: T, right: TRight) => TResult, 20 | ): AsyncIterableQuery; 21 | 22 | /** 23 | * Returns a sequence of correlated elements tranformation that match a given key. 24 | * The transformation is called on an undefined right value if there is no match. 25 | * @param {Iterable} other 26 | * @param {(element:T,index:number)=>TKey} leftKeySelector left element key selector 27 | * @param {(element:TRight,index:number)=>TKey} rightKeySelector right element key selector 28 | * @param {(left:T,right?:TRight)=>TResult} joinSelector transformation 29 | * @returns AsyncIterable 30 | */ 31 | leftJoin( 32 | other: Iterable, 33 | leftKeySelector: (element: T, index: number) => TKey, 34 | rightKeySelector: (element: TRight, index: number) => TKey, 35 | joinSelector: (left: T, right?: TRight) => TResult, 36 | ): AsyncIterableQuery; 37 | 38 | /** 39 | * Returns a sequence of correlated elements where each element from the current sequence 40 | * is matched with zero or more elements from the other sequence. 41 | * @param {Iterable} other 42 | * @param {(element:T,index:number)=>TKey} leftKeySelector left element key selector 43 | * @param {(element:TRight,index:number)=>TKey} rightKeySelector right element key selector 44 | * @param {(left:T,right:TRight)=>TResult} joinSelector transformation 45 | * @returns AsyncIterable 46 | * @todo review name 47 | */ 48 | groupJoin( 49 | other: Iterable, 50 | leftKeySelector: (element: T, index: number) => TKey, 51 | rightKeySelector: (element: TRight, index: number) => TKey, 52 | joinSelector: (left: T, right: TRight[]) => TResult, 53 | ): AsyncIterableQuery; 54 | } 55 | -------------------------------------------------------------------------------- /lib/types/AsyncIterablePredicate.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Apply a predicate over an iterable. 3 | */ 4 | export interface AsyncIterablePredicate extends AsyncIterable { 5 | /** 6 | * Tests whether all the elements pass the predicate. 7 | * @param {(element:T, index:number)=>boolean} predicate element predicate 8 | * @returns Promise 9 | */ 10 | every(predicate: (element: T, index: number) => boolean): Promise; 11 | 12 | /** 13 | * Tests whether at least one element passes the predicate. 14 | * @param {(element:T,index:number)=>boolean} predicate element predicate 15 | * @returns Promise 16 | */ 17 | some(predicate: (element: T, index: number) => boolean): Promise; 18 | 19 | /** 20 | * Determines whether the sequence includes a certain element. 21 | * @param element element to search 22 | * @returns Promise 23 | */ 24 | includes(element: T): Promise; 25 | 26 | /** 27 | * Determines whether the sequence includes a certain element. 28 | * @param element element to search 29 | * @param fromIndex the start index 30 | * @returns Promise 31 | */ 32 | includes(element: T, fromIndex?: number): Promise; 33 | } 34 | -------------------------------------------------------------------------------- /lib/types/AsyncIterableQuery.ts: -------------------------------------------------------------------------------- 1 | import { AsyncIterableJoin } from './AsyncIterableJoin'; 2 | import { AsyncIterablePredicate } from './AsyncIterablePredicate'; 3 | import { AsyncIterableFilter } from './AsyncIterableFilter'; 4 | import { AsyncIterableSet } from './AsyncIterableSet'; 5 | import { AsyncIterableTransformation } from './AsyncIterableTransformation'; 6 | import { AsyncIterableValue } from './AsyncIterableValue'; 7 | 8 | export interface AsyncIterableQuery extends 9 | AsyncIterableValue, 10 | AsyncIterablePredicate, 11 | AsyncIterableFilter, 12 | AsyncIterableTransformation, 13 | AsyncIterableSet, 14 | AsyncIterableJoin, 15 | AsyncIterable { 16 | 17 | /** 18 | * Returns a sequence of key/value pair for each element and its index. 19 | * @returns AsyncIterable<[number,T]> 20 | */ 21 | entries(): AsyncIterableQuery<[number, T]>; 22 | 23 | /** 24 | * Returns a sequence of keys for each index in the source sequence. 25 | * @returns AsyncIterable 26 | */ 27 | keys(): AsyncIterableQuery; 28 | 29 | /** 30 | * Returns a sequence of values for each index in the source sequence. 31 | * @returns AsyncIterable 32 | */ 33 | values(): AsyncIterableQuery; 34 | 35 | /** 36 | * Concatenates the sequence with another one. 37 | * @param {T | Promise | Iterable | Iterable} other 38 | * @returns AsyncIterable 39 | */ 40 | concat(other: T | Promise | Iterable | AsyncIterable): AsyncIterableQuery; 41 | 42 | /** 43 | * Returns a sequence with given elements at the beggining. 44 | * @param {T | Promise | Iterable} other 45 | * @returns AsyncIterable 46 | * @todo review name 47 | */ 48 | prepend(other: T | Promise | Iterable | AsyncIterable): AsyncIterableQuery; 49 | } 50 | -------------------------------------------------------------------------------- /lib/types/AsyncIterableSet.ts: -------------------------------------------------------------------------------- 1 | import { AsyncIterableQuery } from './AsyncIterableQuery'; 2 | 3 | /** 4 | * Produce a new iterable that is a set iterable of unique elements. 5 | */ 6 | export interface AsyncIterableSet extends AsyncIterable { 7 | /** 8 | * Returns a sequence of unique elements. 9 | * @returns Iterable 10 | */ 11 | distinct(): AsyncIterableQuery; 12 | 13 | /** 14 | * Returns a sequence of unique element transformations. 15 | * @param {(element:T)=>S} selector element transformation 16 | * @returns AsyncIterable 17 | */ 18 | distinct(selector: (element: T) => S): AsyncIterableQuery; 19 | 20 | /** 21 | * Returns a sequence of unique elements not contained in a given sequence. 22 | * @param {Iterable} other element transformation 23 | * @returns AsyncIterable 24 | */ 25 | exclude(other: Iterable): AsyncIterableQuery; 26 | 27 | /** 28 | * Returns a sequence of unique elements not contained in a given sequence 29 | * using a transformation for value comparisons. 30 | * @param {Iterable} other items to compare and exclude 31 | * @param {(element: T)=>S} selector element transformation 32 | * @returns Iterable 33 | */ 34 | exclude(other: Iterable, selector: (element: T) => S): AsyncIterableQuery; 35 | 36 | /** 37 | * Returns a set intersection with a given sequence. 38 | * @param {Iterable} other 39 | * @returns AsyncIterable 40 | */ 41 | intersect(other: Iterable): AsyncIterableQuery; 42 | 43 | /** 44 | * Returns a set intersection with a given sequence using a transformation for comparisons. 45 | * @param {Iterable} other 46 | * @pa{(element: T)=>S} element transformation 47 | * @returns AsyncIterable 48 | */ 49 | intersect(other: Iterable, selector: (element: T) => S): AsyncIterableQuery; 50 | 51 | /** 52 | * Returns a set union with a given sequence. 53 | * @param {Iterable} other 54 | * @returns AsyncIterable 55 | */ 56 | union(other: AsyncIterable): AsyncIterableQuery; 57 | 58 | /** 59 | * Returns a set union with a given sequence using a transformation for comparisons. 60 | * @param {Iterable} other 61 | * @pa{(element: T)=>S} element transformation 62 | * @returns AsyncIterable 63 | */ 64 | union(other: AsyncIterable, selector: (element: T) => S): AsyncIterableQuery; 65 | } 66 | -------------------------------------------------------------------------------- /lib/types/AsyncIterableTransformation.ts: -------------------------------------------------------------------------------- 1 | import { AsyncIterableQuery } from './AsyncIterableQuery'; 2 | 3 | /** 4 | * Transform an iterable to a new iterable. 5 | */ 6 | export interface AsyncIterableTransformation extends AsyncIterable { 7 | /** 8 | * Returns a sequence of transformed values. 9 | * @param {(element:T,index:number)=>S} selector element transformation 10 | * @returns AsyncIterable 11 | */ 12 | map(selector: (element: T, index: number) => S): AsyncIterableQuery; 13 | 14 | /** 15 | * Returns a sequence with all sub-sequences concatenated. 16 | * @param {(element:T,index:number)=>Iterable} selector sub-sequence 17 | * @returns AsyncIterableQuery 18 | */ 19 | flat(selector: (element: T, index: number) => AsyncIterable): AsyncIterable; 20 | 21 | /** 22 | * Returns a sequence with all sub-sequences concatenated. 23 | * @returns AsyncIterableQuery 24 | */ 25 | flat(): AsyncIterableQuery; 26 | } 27 | -------------------------------------------------------------------------------- /lib/types/AsyncIterableValue.ts: -------------------------------------------------------------------------------- 1 | import { IterableQuery } from 'itiriri'; 2 | 3 | /** 4 | * Calculate a scalar value over an iterable. 5 | */ 6 | export interface AsyncIterableValue extends AsyncIterable { 7 | /** 8 | * Returns the element at a specified index. 9 | * For a negative index returns the element from the end of the sequence. 10 | * If index is out of the range, returns undefined. 11 | * @param {number} index element's index 12 | * @returns Promise 13 | */ 14 | nth(index: number): Promise; 15 | 16 | /** 17 | * Returns a promise with the iterable from the original async iterable 18 | * @returns Promise> 19 | */ 20 | awaitAll(): Promise>; 21 | 22 | /** 23 | * Returns the first index at which a given element can be found. 24 | * If not present, returns -1. 25 | * @param {T} element element to search 26 | * @returns Promise 27 | */ 28 | indexOf(element: T): Promise; 29 | 30 | /** 31 | * Returns the first index at which a given element can be found. 32 | * If not present, returns -1. 33 | * @param {T} element element to search 34 | * @param fromIndex the start index 35 | * @returns Promise 36 | */ 37 | indexOf(element: T, fromIndex: number): Promise; 38 | 39 | /** 40 | * Finds the first index at which a given element satisfies the specified predicate. 41 | * If not present, returns -1. 42 | * @param {(element:T)=>boolean} predicate element predicate 43 | * @returns Promise 44 | */ 45 | findIndex(predicate: (element: T, index: number) => boolean): Promise; 46 | 47 | /** 48 | * Returns the last index at which a given element can be found. 49 | * If not present, returns -1. 50 | * @param {T} element element to search 51 | * @returns Promise 52 | */ 53 | lastIndexOf(element: T): Promise; 54 | 55 | /** 56 | * Returns the last index at which a given element can be found. 57 | * If not present, returns -1. 58 | * @param {T} element element to search 59 | * @param fromIndex the start index 60 | * @returns Promise 61 | */ 62 | lastIndexOf(element: T, fromIndex: number): Promise; 63 | 64 | /** 65 | * Finds the last index at which a given element satisfies the specified predicate. 66 | * If not present, returns -1. 67 | * @param {T} element element to search 68 | * @returns Promise 69 | */ 70 | findLastIndex(predicate: (element: T, index: number) => boolean): Promise; 71 | 72 | /** 73 | * Returns the number of elements. 74 | * @returns Promise 75 | */ 76 | length(): Promise; 77 | 78 | /** 79 | * Returns the number of elements matching the specified predicate. 80 | * @param {(element:T,index:number)=>boolean} predicate element predicate 81 | * @returns Promise 82 | */ 83 | length(predicate: (element: T, index: number) => boolean): Promise; 84 | 85 | /** 86 | * Returns the first element. 87 | * For an empty sequence returns undefined. 88 | * @returns Promise 89 | */ 90 | first(): Promise; 91 | 92 | /** 93 | * Finds the first element that satisfies the specified predicate. 94 | * If no element satisfies the predicate, returns undefined. 95 | * @param {(element:T,index:number)=>boolean} predicate element predicate 96 | * @returns Promise 97 | */ 98 | find(predicate: (element: T, index: number) => boolean): Promise; 99 | 100 | /** 101 | * Returns the last element. 102 | * For an empty sequence returns undefined. 103 | * @returns Promise 104 | */ 105 | last(): Promise; 106 | 107 | /** 108 | * Finds the last element that satisfies the specified predicate. 109 | * If no element satisfies the predicate, returns undefined. 110 | * @param {(element:T,index:number)=>boolean} predicate element predicate 111 | * @returns Promise 112 | */ 113 | findLast(predicate: (element: T, index: number) => boolean): Promise; 114 | 115 | /** 116 | * Returns the average value. 117 | * If sequence is empty, returns undefined. 118 | * @returns Promise 119 | */ 120 | average(): Promise; 121 | 122 | /** 123 | * Returns the average value over a sequence of transformed values. 124 | * If sequence is empty, returns undefined. 125 | * @param {(element:T,index:number)=>number} selector element transformation 126 | * @returns Promise 127 | */ 128 | average(selector: (element: T, index: number) => number): Promise; 129 | 130 | /** 131 | * Returns the minimum value. 132 | * If sequence is empty, returns undefined. 133 | * @returns Promise 134 | */ 135 | min(): Promise; 136 | 137 | /** 138 | * Returns the minimum value from a sequence using a comparer function. 139 | * If sequence is empty, returns undefined. 140 | * @param {(element1:T,element2:T)=>number} compareFn comparer function that returns -1 141 | * for element1element2, 0 for equal values 142 | * @returns Promise 143 | */ 144 | min(compareFn: (element1: T, element2: T) => number): Promise; 145 | 146 | /** 147 | * Returns the maximum value. 148 | * If sequence is empty, returns undefined. 149 | * @returns Promise 150 | */ 151 | max(): Promise; 152 | 153 | /** 154 | * Returns the maximum value from a sequence using a compare function. 155 | * If sequence is empty, returns undefined. 156 | * @param {(element1:T,element2:T)=>number} compareFn comparer function that returns -1 157 | * for element1element2, 0 for equal values 158 | * @returns Promise 159 | */ 160 | max(compareFn: (element1: T, element2: T) => number): Promise; 161 | 162 | /** 163 | * Returns the sum of all elements. 164 | * If sequence is empty, returns undefined. 165 | * @returns Promise 166 | */ 167 | sum(): Promise; 168 | 169 | /** 170 | * Returns the sum of elements from a sequence of transformed values. 171 | * If sequence is empty, returns undefined. 172 | * @param {(element:T,index:number)=>number} selector element transformation 173 | * @returns Promise 174 | */ 175 | sum(selector: (element: T, index: number) => number): Promise; 176 | 177 | /** 178 | * Applies a function against an accumulator and each element to reduce it to a single value 179 | * (from left to right). 180 | * @param {(accumulator:TResult,current:T,index:number)=>TResult} callback accumulator function 181 | * @returns Promise 182 | */ 183 | reduce( 184 | callback: (accumulator: T, current: T, index: number) => T, 185 | ): Promise; 186 | 187 | /** 188 | * Applies a function against an accumulator and each element to reduce it to a single value 189 | * (from left to right). 190 | * @param {(accumulator:S,current:T,index:number)=>S} callback accumulator function 191 | * @param {S} initialValue initial value 192 | * @returns Promise 193 | */ 194 | reduce( 195 | callback: (accumulator: S, current: T, index: number) => S, 196 | initialValue: S, 197 | ): Promise; 198 | 199 | /** 200 | * Runs through every element and applies a given function. 201 | * @param {(element:T,index:number)=>void} action function to apply on each element 202 | * @returns Promise 203 | */ 204 | forEach(action: (element: T, index: number) => void): Promise; 205 | } 206 | -------------------------------------------------------------------------------- /lib/utils/asyncIterable.ts: -------------------------------------------------------------------------------- 1 | import { asyncIterator } from './asyncIterator'; 2 | 3 | export function asyncIterable( 4 | target: () => AsyncIterable, 5 | ): AsyncIterable { 6 | return { 7 | [Symbol.asyncIterator]() { 8 | return asyncIterator(target()); 9 | }, 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /lib/utils/asyncIterator.ts: -------------------------------------------------------------------------------- 1 | export function asyncIterator(source: AsyncIterable): AsyncIterator { 2 | return source[Symbol.asyncIterator](); 3 | } 4 | -------------------------------------------------------------------------------- /lib/utils/isAsyncIterable.ts: -------------------------------------------------------------------------------- 1 | export function isAsyncIterable(item: any): item is AsyncIterable { 2 | return typeof (>item)[Symbol.asyncIterator] === 'function'; 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "itiriri-async", 3 | "version": "2.0.0", 4 | "license": "MIT", 5 | "description": "A library built for async iteration protocol.", 6 | "private": true, 7 | "scripts": { 8 | "test": "gulp test", 9 | "coverage": "istanbul cover node_modules/mocha/bin/_mocha ./build/compiled/test ./build/compiled/lib -- --recursive" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/labs42io/itiriri-async.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/labs42io/itiriri-async/issues" 17 | }, 18 | "keywords": [ 19 | "iterator", 20 | "iterable", 21 | "async", 22 | "asynciterator", 23 | "filter", 24 | "map", 25 | "query", 26 | "collections", 27 | "deferred", 28 | "lazy" 29 | ], 30 | "author": { 31 | "name": "Labs42", 32 | "url": "https://labs42.io" 33 | }, 34 | "contributors": [ 35 | "Dumitru Deveatii ", 36 | "Vlad Negura " 37 | ], 38 | "dependencies": { 39 | "itiriri": "2.0.1" 40 | }, 41 | "devDependencies": { 42 | "@types/benchmark": "1.0.30", 43 | "@types/chai": "4.1.7", 44 | "@types/chai-as-promised": "7.1.0", 45 | "@types/mocha": "5.2.6", 46 | "@types/node": "10.12.21", 47 | "@types/sinon": "7.0.11", 48 | "@types/uuid": "3.4.4", 49 | "benchmark": "^2.1.4", 50 | "browserify": "16.2.3", 51 | "chai": "4.2.0", 52 | "chai-as-promised": "7.1.1", 53 | "chai-spies": "1.0.0", 54 | "del": "4.1.1", 55 | "gulp": "4.0.2", 56 | "gulp-mocha": "6.0.0", 57 | "gulp-replace": "1.0.0", 58 | "gulp-shell": "0.7.0", 59 | "gulp-sourcemaps": "2.6.5", 60 | "gulp-tslint": "8.1.4", 61 | "gulp-typescript": "5.0.1", 62 | "gulp-uglify-es": "1.0.4", 63 | "gulpclass": "0.2.0", 64 | "istanbul": "0.4.5", 65 | "mocha": "6.1.4", 66 | "mocha-lcov-reporter": "1.3.0", 67 | "sinon": "7.2.3", 68 | "sinon-chai": "3.3.0", 69 | "ts-node": "8.1.0", 70 | "tslib": "^1.9.3", 71 | "tslint": "5.16.0", 72 | "tslint-config-airbnb": "5.11.1", 73 | "typescript": "3.4.5", 74 | "vinyl-buffer": "1.0.1", 75 | "vinyl-source-stream": "2.0.0", 76 | "web-request": "1.0.7" 77 | } 78 | } -------------------------------------------------------------------------------- /test/helpers/SpyAsyncIterable.ts: -------------------------------------------------------------------------------- 1 | export class SpyAsyncIterable implements AsyncIterable { 2 | private iterated: boolean = false; 3 | constructor(private readonly source: AsyncIterable) { } 4 | 5 | get wasIterated() { 6 | return this.iterated; 7 | } 8 | 9 | [Symbol.asyncIterator](): AsyncIterator { 10 | this.iterated = true; 11 | return this.source[Symbol.asyncIterator](); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/helpers/SpyIterable.ts: -------------------------------------------------------------------------------- 1 | export class SpyIterable implements Iterable { 2 | private iterated: boolean = false; 3 | constructor(private readonly source: Iterable) { } 4 | 5 | get wasIterated() { 6 | return this.iterated; 7 | } 8 | 9 | [Symbol.iterator](): Iterator { 10 | this.iterated = true; 11 | return this.source[Symbol.iterator](); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/helpers/asyncGenerators.ts: -------------------------------------------------------------------------------- 1 | export async function* empty() { } 2 | 3 | export async function* numbers(offset = 0, step = 1) { 4 | let i = offset; 5 | while (1) { 6 | yield await Promise.resolve(i); 7 | i += step; 8 | } 9 | } 10 | 11 | export async function* fromArray(arr) { 12 | for (const e of arr) { 13 | yield await e; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/helpers/toArray.ts: -------------------------------------------------------------------------------- 1 | export async function toArray(iterator: AsyncIterable) { 2 | const result: T[] = []; 3 | for await (const element of iterator) { 4 | result.push(element); 5 | } 6 | 7 | return result; 8 | } 9 | -------------------------------------------------------------------------------- /test/iterators/concat.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { concat } from '../../lib/iterators/concat'; 3 | import { asyncIterator } from '../../lib/utils/asyncIterator'; 4 | import { empty, fromArray } from '../helpers/asyncGenerators'; 5 | 6 | describe('iterators/concat', () => { 7 | describe('When accessing the iterator', () => { 8 | it('Should return an AsyncIterator', async () => { 9 | const iterator = concat(empty(), empty()); 10 | 11 | expect(asyncIterator(iterator)).to.have.property('next'); 12 | }); 13 | }); 14 | 15 | describe('When called on empty source', () => { 16 | it('Should return empty source', async () => { 17 | const iterator = concat(empty(), empty()); 18 | 19 | expect(await asyncIterator(iterator).next()).to.have.property('done').that.is.true; 20 | }); 21 | }); 22 | 23 | describe('When called on some sources', () => { 24 | it('Should return 4 elements', async () => { 25 | const iterator = concat(fromArray([13]), fromArray([11, -1, 1])); 26 | const expectedValues = [13, 11, -1, 1]; 27 | let i = 0; 28 | 29 | for await (const element of iterator) { 30 | expect(element).to.be.equal(expectedValues[i++]); 31 | } 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/iterators/distinct.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import { expect } from 'chai'; 3 | import { distinct } from '../../lib/iterators/distinct'; 4 | import { fromArray } from '../helpers/asyncGenerators'; 5 | import { asyncIterator } from '../../lib/utils/asyncIterator'; 6 | import { toArray } from '../helpers/toArray'; 7 | 8 | describe('iterators/distinct', () => { 9 | describe('when called multiple times', () => { 10 | it('Should return new iterator on each call', async () => { 11 | const source = fromArray([1, 2, 3]); 12 | const result = distinct(source, x => x); 13 | 14 | expect(asyncIterator(result)).not.equals(asyncIterator(result)); 15 | }); 16 | }); 17 | 18 | describe('When source is empty', () => { 19 | it('Should return completed iterator', async () => { 20 | const source = []; 21 | const iterator = distinct(fromArray(source), x => x); 22 | 23 | expect(await toArray(iterator)).to.deep.equal([]); 24 | }); 25 | }); 26 | 27 | describe('When source has unique elements', () => { 28 | it('Should return the elements', async () => { 29 | const arr = [1, 2, 3]; 30 | const iterator = distinct(fromArray(arr), x => x); 31 | const result = await toArray(iterator); 32 | 33 | expect(result).to.deep.equal(arr); 34 | }); 35 | }); 36 | 37 | describe('When source has unique element keys', () => { 38 | it('Should return the elements', async () => { 39 | const arr = [{ p: 1 }, { p: 2 }, { p: 3 }]; 40 | const iterator = distinct(fromArray(arr), x => x.p); 41 | const result = await toArray(iterator); 42 | 43 | expect(result).to.deep.equal(arr); 44 | }); 45 | }); 46 | 47 | describe('When source has all elements same', () => { 48 | it('Should return the unique element', async () => { 49 | const arr = [42, 42, 42]; 50 | const iterator = distinct(fromArray(arr), x => x); 51 | const result = await toArray(iterator); 52 | 53 | expect(result).to.deep.equal([42]); 54 | }); 55 | }); 56 | 57 | describe('When source has duplicate elements', () => { 58 | it('Should return unique elements', async () => { 59 | const arr = [1, 42, 2, 42, 42]; 60 | const iterator = distinct(fromArray(arr), x => x); 61 | const result = await toArray(iterator); 62 | 63 | expect(result).to.deep.equal([1, 42, 2]); 64 | }); 65 | }); 66 | 67 | describe('When source has all element keys same', () => { 68 | it('Should return the unique element', async () => { 69 | const elem = { p: 42 }; 70 | const arr = [elem, elem, elem]; 71 | const iterator = distinct(fromArray(arr), x => x.p); 72 | const result = await toArray(iterator); 73 | 74 | expect(result).to.deep.equal([elem]); 75 | }); 76 | }); 77 | 78 | describe('When source has duplicate element keys', () => { 79 | it('Should return unique elements by their first occurence', async () => { 80 | const elem1 = { p: 1 }; 81 | const elem42 = { p: 42 }; 82 | const elem2 = { p: 2 }; 83 | const arr = [elem1, elem42, elem2, { p: 42 }, { p: 42 }]; 84 | const iterator = distinct(fromArray(arr), x => x.p); 85 | const result = await toArray(iterator); 86 | 87 | expect(result).to.deep.equal([elem1, elem42, elem2]); 88 | }); 89 | }); 90 | 91 | describe('When calling on some rejected Promises', () => { 92 | it('Should return rejected Promise', async () => { 93 | const source = [Promise.reject(1)]; 94 | const q = distinct(fromArray(source), x => x); 95 | 96 | expect(toArray(q)).to.eventually.be.rejected; 97 | }); 98 | }); 99 | 100 | describe('When calling on some Promises', () => { 101 | it('Should return rejected Promise', async () => { 102 | // hitting all tests (coverage) 103 | const source = [ 104 | Promise.resolve(1).finally(), 105 | Promise.reject(1).finally(), 106 | Promise.resolve(1), 107 | ]; 108 | const q = distinct(fromArray(source), x => x); 109 | 110 | expect(toArray(q)).to.eventually.be.rejected; 111 | }); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /test/iterators/exclude.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { exclude } from '../../lib/iterators/exclude'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | import { asyncIterator } from '../../lib/utils/asyncIterator'; 5 | import { toArray } from '../helpers/toArray'; 6 | 7 | describe('iterators/exclude', () => { 8 | describe('when called multiple times', () => { 9 | it('Should return new iterator on each call', async () => { 10 | const left = [1, 2]; 11 | const right = [3, 4, 5]; 12 | const it1 = exclude(fromArray(left), right, x => x); 13 | const it2 = exclude(fromArray(left), right, x => x); 14 | expect(it1).not.equals(it2); 15 | }); 16 | }); 17 | 18 | describe('When source is empty', () => { 19 | it('Should return completed iterator', async () => { 20 | const left = []; 21 | const right = []; 22 | const it = asyncIterator(exclude(fromArray(left), right, x => x)); 23 | 24 | expect(await it.next()).to.have.property('done').that.is.true; 25 | }); 26 | 27 | it('Should return completed iterator for non-empty exclusions', async () => { 28 | const left = []; 29 | const right = [1, 2, 3]; 30 | const it = asyncIterator(exclude(fromArray(left), right, x => x)); 31 | 32 | expect(await it.next()).to.have.property('done').that.is.true; 33 | }); 34 | }); 35 | 36 | describe('When source has elemements', () => { 37 | describe('And exclusion is empty', () => { 38 | it('Should return elements from source', async () => { 39 | const left = [1, 2, 3]; 40 | const right = []; 41 | const iterator = exclude(fromArray(left), right, x => x); 42 | const result = await toArray(iterator); 43 | 44 | expect(result).to.deep.equal([1, 2, 3]); 45 | }); 46 | }); 47 | 48 | describe('And is disjoint with exclusion', () => { 49 | it('Should return elements from source', async () => { 50 | const left = [1, 2, 3]; 51 | const right = [4, 5]; 52 | const iterator = exclude(fromArray(left), right, x => x); 53 | const result = await toArray(iterator); 54 | 55 | expect(result).to.deep.equal([1, 2, 3]); 56 | }); 57 | }); 58 | 59 | describe('And source has same elements', () => { 60 | it('Should return empty iterator', async () => { 61 | const left = [1, 2, 3]; 62 | const right = [1, 2, 3]; 63 | const it = asyncIterator(exclude(fromArray(left), right, x => x)); 64 | 65 | expect(await it.next()).to.have.property('done').that.is.true; 66 | }); 67 | }); 68 | 69 | describe('And source has some elements in common', () => { 70 | it('Should return correct result', async () => { 71 | const left = [1, 2, 3]; 72 | const right = [2, 3, 4]; 73 | const iterator = exclude(fromArray(left), right, x => x); 74 | const result = await toArray(iterator); 75 | 76 | expect(result).to.deep.equal([1]); 77 | }); 78 | }); 79 | 80 | describe('And source has some elements in common', () => { 81 | it('Should return correct result for a key selector', async () => { 82 | const elem1 = { p: 1 }; 83 | const elem2 = { p: 2 }; 84 | const elem3 = { p: 3 }; 85 | const elem4 = { p: 4 }; 86 | const left = [elem1, elem2, elem3, { p: 1 }]; 87 | const right = [{ p: 2 }, elem3, elem4]; 88 | const iterator = exclude(fromArray(left), right, x => x.p); 89 | const result = await toArray(iterator); 90 | 91 | expect(result).to.deep.equal([elem1, { p: 1 }]); 92 | }); 93 | }); 94 | 95 | describe('And source has some duplicate elements in common', () => { 96 | it('Should return correct result', async () => { 97 | const left = [1, 1, 2, 3, 3]; 98 | const right = [2, 2, 3, 3, 4]; 99 | const iterator = exclude(fromArray(left), right, x => x); 100 | const result = await toArray(iterator); 101 | 102 | expect(result).to.deep.equal([1, 1]); 103 | }); 104 | }); 105 | }); 106 | 107 | describe('When calling on some Promises', () => { 108 | it('Should return rejected Promise', async () => { 109 | // hitting all tests (coverage) 110 | const source = [ 111 | Promise.resolve(1).finally(), 112 | Promise.reject(1).finally(), 113 | Promise.resolve(1), 114 | ]; 115 | const q = exclude(fromArray(source), [], x => x); 116 | 117 | expect(toArray(q)).to.eventually.be.rejected; 118 | }); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /test/iterators/filter.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { filter } from '../../lib/iterators/filter'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | import { asyncIterator } from '../../lib/utils/asyncIterator'; 5 | import { toArray } from '../helpers/toArray'; 6 | 7 | describe('iterators/filter', () => { 8 | describe('when called multiple times', () => { 9 | it('Should return new iterator on each call', async () => { 10 | const source = [1, 2, 3]; 11 | const result = filter(fromArray(source), () => true); 12 | 13 | expect(asyncIterator(result)).not.equals(asyncIterator(result)); 14 | }); 15 | }); 16 | 17 | describe('When source is empty', () => { 18 | it('Should return completed iterator', async () => { 19 | const source = []; 20 | const iterator = filter(fromArray(source), () => true); 21 | 22 | expect(await toArray(iterator)).to.deep.equal([]); 23 | }); 24 | }); 25 | 26 | describe('When predicate is always true', () => { 27 | it('Should return all elements', async () => { 28 | const source = [1, 2, 3]; 29 | const iterator = filter(fromArray(source), () => true); 30 | const result = await toArray(iterator); 31 | 32 | expect(result).to.deep.equal([1, 2, 3]); 33 | }); 34 | }); 35 | 36 | describe('When predicate is always false', () => { 37 | it('Should return completed iterator', async () => { 38 | const source = [1, 2, 3]; 39 | const iterator = filter(fromArray(source), () => false); 40 | 41 | expect(await toArray(iterator)).to.deep.equal([]); 42 | }); 43 | }); 44 | 45 | describe('When predicate matches odd indexes', () => { 46 | it('Should return elements', async () => { 47 | const source = [1, 42, 3, 4242]; 48 | const iterator = filter(fromArray(source), (_, i) => i % 2 === 1); 49 | const result = await toArray(iterator); 50 | 51 | expect(result).to.deep.equal([42, 4242]); 52 | }); 53 | }); 54 | 55 | describe('When calling on some Promises', () => { 56 | it('Should return rejected Promise', async () => { 57 | // hitting all tests (coverage) 58 | const source = [ 59 | Promise.resolve(1).finally(), 60 | Promise.reject(1).finally(), 61 | Promise.resolve(1), 62 | ]; 63 | const q = filter(fromArray(source), x => x); 64 | 65 | expect(toArray(q)).to.eventually.be.rejected; 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/iterators/flat.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { flat } from '../../lib/iterators/flat'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | import { toArray } from '../helpers/toArray'; 5 | import { asyncIterator } from '../../lib/utils/asyncIterator'; 6 | 7 | describe('iterators/flat', () => { 8 | describe('when called multiple times', () => { 9 | it('Should return new iterator on each call', async () => { 10 | const left = fromArray([1, 2, 3]); 11 | const right = fromArray([4, 5]); 12 | const source = [left, right]; 13 | 14 | expect(flat(source)).not.equals(flat(source)); 15 | }); 16 | }); 17 | 18 | describe('When source is empty', () => { 19 | it('Should return completed iterator', async () => { 20 | const source = []; 21 | const it = asyncIterator(flat(fromArray(source))); 22 | 23 | expect(await it.next()) 24 | .to.have.property('done') 25 | .that.is.true; 26 | }); 27 | }); 28 | 29 | describe('When left source is empty', () => { 30 | it('Should return elements from right source', async () => { 31 | const left = fromArray([]); 32 | const right = fromArray([4, 5]); 33 | const source = [left, right]; 34 | const iterator = flat(source); 35 | const result = await toArray(iterator); 36 | 37 | expect(result).to.deep.equal([4, 5]); 38 | }); 39 | }); 40 | 41 | describe('When right source is empty', () => { 42 | it('Should return elements from left source', async () => { 43 | const left = fromArray([1, 2, 3]); 44 | const right = fromArray([]); 45 | const source = [left, right]; 46 | const iterator = flat(source); 47 | const result = await toArray(iterator); 48 | 49 | expect(result).to.deep.equal([1, 2, 3]); 50 | }); 51 | }); 52 | 53 | describe('When has multiple iterables with elements', () => { 54 | it('Should return elements from left source', async () => { 55 | const source1 = fromArray([1, 2, 3]); 56 | const source2 = fromArray([4, 5, 1]); 57 | const source3 = fromArray([42]); 58 | 59 | const source = [source1, source2, source3]; 60 | const iterator = flat(source); 61 | const result = await toArray(iterator); 62 | 63 | expect(result).to.deep.equal([1, 2, 3, 4, 5, 1, 42]); 64 | }); 65 | }); 66 | 67 | describe('When calling on some Promises', () => { 68 | it('Should return rejected Promise', () => { 69 | // hitting all tests (coverage) 70 | const source = [ 71 | [ 72 | Promise.resolve(-1).finally(), 73 | ], 74 | [ 75 | Promise.resolve(1), 76 | Promise.reject(0).finally(), 77 | ], 78 | ]; 79 | const q = flat(fromArray(source)); 80 | 81 | expect(toArray(q)).to.eventually.be.rejected; 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /test/iterators/groupJoin.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { groupJoin } from '../../lib/iterators/groupJoin'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | import { toArray } from '../helpers/toArray'; 5 | 6 | describe('iterators/groupJoin', () => { 7 | describe('When called on empty sources', () => { 8 | it('Should return empty sources', async () => { 9 | const source = []; 10 | const others = []; 11 | const iterator = groupJoin(fromArray(source), others, x => x, x => x, x => x); 12 | 13 | expect(await toArray(iterator)).to.be.deep.equal([]); 14 | }); 15 | }); 16 | 17 | describe('When called on some sources', () => { 18 | it('Should return 3 grupped elemnts', async () => { 19 | const source = [1, 2, 3]; 20 | const others = [2, 2, 3, 3, 3, 4, 4]; 21 | const iterator = groupJoin(fromArray(source), others, x => x, x => x, (x, y) => ({ x, y })); 22 | 23 | expect(await toArray(iterator)).to.be.deep.equal([ 24 | { x: 1, y: [] }, 25 | { x: 2, y: [2, 2] }, 26 | { x: 3, y: [3, 3, 3] }, 27 | ]); 28 | }); 29 | 30 | it('Should return 2 grupped elemnts', async () => { 31 | const source = [ 32 | { val: 2, other: 'asdf' }, 33 | { val: 3, other: 'ad' }, 34 | ]; 35 | const others = [ 36 | { val: 3, y: 'a' }, 37 | { val: 3, y: 'b' }, 38 | ]; 39 | const iterator = groupJoin( 40 | fromArray(source), 41 | others, 42 | x => x.val, 43 | x => x.val, 44 | (x, y) => ({ y, x: x.val }), 45 | ); 46 | 47 | expect(await toArray(iterator)).to.be.deep.equal([ 48 | { x: 2, y: [] }, 49 | { x: 3, y: [{ val: 3, y: 'a' }, { val: 3, y: 'b' }] }, 50 | ]); 51 | }); 52 | }); 53 | 54 | describe('When calling on some Promises', () => { 55 | it('Should return rejected Promise', async () => { 56 | // hitting all tests (coverage) 57 | const source = [ 58 | Promise.resolve(1).finally(), 59 | Promise.reject(1).finally(), 60 | Promise.resolve(1), 61 | ]; 62 | const q = groupJoin(fromArray(source), [], x => x, x => x, x => x); 63 | 64 | expect(toArray(q)).to.eventually.be.rejected; 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/iterators/intersect.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { intersect } from '../../lib/iterators/intersect'; 3 | import { toArray } from '../helpers/toArray'; 4 | import { fromArray } from '../helpers/asyncGenerators'; 5 | 6 | describe('iterators/intersect', () => { 7 | describe('When called on empty sources', () => { 8 | it('Should return empty source', async () => { 9 | const source = []; 10 | const others = []; 11 | const iterator = intersect(fromArray(source), others, x => x); 12 | 13 | expect(await toArray(iterator)).to.be.deep.equal([]); 14 | }); 15 | }); 16 | 17 | describe('When called on some sources', () => { 18 | it('Should return all 3 elements', async () => { 19 | const source = [3, 4, 5]; 20 | const others = [3, 4, 5]; 21 | const iterator = intersect(fromArray(source), others, x => x); 22 | 23 | expect(await toArray(iterator)).to.be.deep.equal([3, 4, 5]); 24 | }); 25 | 26 | it('Should return all 4 elements', async () => { 27 | const source = [3, 44, 5, 1]; 28 | const others = [3, 1, 5, 44]; 29 | const iterator = intersect(fromArray(source), others, x => x); 30 | 31 | expect(await toArray(iterator)).to.be.deep.equal([3, 44, 5, 1]); 32 | }); 33 | 34 | it('Should return 2 elements', async () => { 35 | const source = [3, 44, 5, 1]; 36 | const others = [3, 7, 6, 44]; 37 | const iterator = intersect(fromArray(source), others, x => x); 38 | 39 | expect(await toArray(iterator)).to.be.deep.equal([3, 44]); 40 | }); 41 | 42 | it('Should return 1 element', async () => { 43 | const source = [3, 44, 5, 1, 20]; 44 | const others = [1, 1, 51, 444, 2]; 45 | const iterator = intersect(fromArray(source), others, x => x); 46 | 47 | expect(await toArray(iterator)).to.be.deep.equal([1]); 48 | }); 49 | 50 | it('Should return empty source', async () => { 51 | const source = [3, 44, 5, 1]; 52 | const others = [11, 11, 51, 414]; 53 | const iterator = intersect(fromArray(source), others, x => x); 54 | 55 | expect(await toArray(iterator)).to.be.deep.equal([]); 56 | }); 57 | 58 | it('Should return empty source', async () => { 59 | const source = [{ x: 1, y: 'aasdf' }, { x: 2, y: 'fdasd' }]; 60 | const others = [{ x: 2, y: 'asdf' }, { x: 3, y: 'asdf' }]; 61 | const iterator = intersect(fromArray(source), others, x => x.x); 62 | 63 | expect(await toArray(iterator)).to.be.deep.equal([{ x: 2, y: 'fdasd' }]); 64 | }); 65 | }); 66 | 67 | describe('When calling on some Promises', () => { 68 | it('Should return rejected Promise', async () => { 69 | // hitting all tests (coverage) 70 | const source = [ 71 | Promise.resolve(1).finally(), 72 | Promise.reject(1).finally(), 73 | Promise.resolve(1), 74 | ]; 75 | const q = intersect(fromArray(source), [], x => x); 76 | 77 | expect(toArray(q)).to.eventually.be.rejected; 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/iterators/join.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { join } from '../../lib/iterators/join'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | import { toArray } from '../helpers/toArray'; 5 | 6 | describe('iterators/join', () => { 7 | describe('When called on empty source', () => { 8 | it('Should return empty source', async () => { 9 | const source = []; 10 | const others = []; 11 | const iterator = join(fromArray(source), others, x => x, x => x, (e1, e2) => ({ e1, e2 })); 12 | 13 | expect(await toArray(iterator)).to.be.deep.equal([]); 14 | }); 15 | }); 16 | 17 | describe('When called on some source', () => { 18 | it('Should return common values', async () => { 19 | const source = [1, 2, 3, 4]; 20 | const others = [3, 4, 5, 6]; 21 | const iterator = join(fromArray(source), others, x => x, x => x, x => x); 22 | 23 | expect(await toArray(iterator)).to.be.deep.equal([3, 4]); 24 | }); 25 | 26 | it('Should return objects with common property', async () => { 27 | const source = [ 28 | { name: 'Football', awesomeness: 20 }, 29 | { name: 'Chess', awesomeness: 30 }, 30 | { name: 'Hockey', awesomeness: 40 }, 31 | ]; 32 | const others = [ 33 | { name: 'Russia', awesomeness: 30 }, 34 | { name: 'Norway', awesomeness: 20 }, 35 | { name: 'France', awesomeness: 40 }, 36 | ]; 37 | const iterator = join( 38 | fromArray(source), 39 | others, 40 | x => x.awesomeness, 41 | x => x.awesomeness, 42 | (e1, e2) => `Playing ${e1.name} in ${e2.name}`, 43 | ); 44 | 45 | expect(await toArray(iterator)).to.be.deep.equal([ 46 | 'Playing Football in Norway', 47 | 'Playing Chess in Russia', 48 | 'Playing Hockey in France', 49 | ]); 50 | }); 51 | }); 52 | 53 | describe('When calling on some Promises', () => { 54 | it('Should return rejected Promise', async () => { 55 | // hitting all tests (coverage) 56 | const source = [ 57 | Promise.resolve(1).finally(), 58 | Promise.reject(1).finally(), 59 | Promise.resolve(1), 60 | ]; 61 | const q = join(fromArray(source), [], x => x, x => x, x => x); 62 | 63 | expect(toArray(q)).to.eventually.be.rejected; 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /test/iterators/leftJoin.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { leftJoin } from '../../lib/iterators/leftJoin'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | import { toArray } from '../helpers/toArray'; 5 | 6 | describe('iterators/leftJoin', () => { 7 | describe('When called on empty source', () => { 8 | it('Should return empty source', async () => { 9 | const source = []; 10 | const others = []; 11 | const iterator = leftJoin(fromArray(source), others, x => x, x => x, x => x); 12 | 13 | expect(await toArray(iterator)).to.be.deep.equal([]); 14 | }); 15 | }); 16 | 17 | describe('When called on some source', () => { 18 | it('Should return 3 joined items', async () => { 19 | const source = [1, 2, 3]; 20 | const others = [2]; 21 | const iterator = leftJoin(fromArray(source), others, x => x, x => x, (x, y) => ({ x, y })); 22 | 23 | expect(await toArray(iterator)).to.be.deep.equal([ 24 | { x: 1, y: undefined }, 25 | { x: 2, y: 2 }, 26 | { x: 3, y: undefined }, 27 | ]); 28 | }); 29 | 30 | it('Should return 2 joined items', async () => { 31 | const source = [1, 3]; 32 | const others = [2]; 33 | const iterator = leftJoin( 34 | fromArray(source), 35 | others, x => x, x => x, (x, y) => ({ y, x: x * 10 }), 36 | ); 37 | 38 | expect(await toArray(iterator)).to.be.deep.equal([ 39 | { x: 10, y: undefined }, 40 | { x: 30, y: undefined }, 41 | ]); 42 | }); 43 | 44 | it('Should return 4 joined items', async () => { 45 | const source = ['a', 'b', 'c', 'd']; 46 | const others = ['add', 'eff', 'ccc']; 47 | const iterator = leftJoin(fromArray(source), others, x => x, x => x[0], (x, y) => ({ x, y })); 48 | 49 | expect(await toArray(iterator)).to.be.deep.equal([ 50 | { x: 'a', y: 'add' }, 51 | { x: 'b', y: undefined }, 52 | { x: 'c', y: 'ccc' }, 53 | { x: 'd', y: undefined }, 54 | ]); 55 | }); 56 | }); 57 | 58 | describe('When calling on some Promises', () => { 59 | it('Should return rejected Promise', async () => { 60 | // hitting all tests (coverage) 61 | const source = [ 62 | Promise.resolve(1).finally(), 63 | Promise.reject(1).finally(), 64 | Promise.resolve(1), 65 | ]; 66 | const q = leftJoin(fromArray(source), [], x => x, x => x, x => x); 67 | 68 | expect(toArray(q)).to.eventually.be.rejected; 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /test/iterators/map.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { map } from '../../lib/iterators/map'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | import { toArray } from '../helpers/toArray'; 5 | 6 | describe('iterators/map', () => { 7 | describe('When applying identity transformation', () => { 8 | it('Should return the same elements', async () => { 9 | const source = [1, 2, 4, 8, 16]; 10 | const iterator = map(fromArray(source), elem => elem); 11 | 12 | expect(await toArray(iterator)).to.be.deep.equal([1, 2, 4, 8, 16]); 13 | }); 14 | it('Should return the same elements', async () => { 15 | const source = [1.1, 2.2, 4.4, 8.8, 16.16]; 16 | const iterator = map(fromArray(source), elem => elem); 17 | 18 | expect(await toArray(iterator)).to.be.deep.equal([1.1, 2.2, 4.4, 8.8, 16.16]); 19 | }); 20 | }); 21 | 22 | describe('When applying linear transformation', () => { 23 | it('Should return the elemens modified', async () => { 24 | const source = [1, 2, 4, 8]; 25 | const iterator = map(fromArray(source), elem => elem * 2 + 2); 26 | 27 | expect(await toArray(iterator)).to.be.deep.equal([4, 6, 10, 18]); 28 | }); 29 | it('Should return the elemens modified', async () => { 30 | const source = ['a', 'b', 'c', 'd']; 31 | const iterator = map(fromArray(source), elem => `${elem}a`); 32 | 33 | expect(await toArray(iterator)).to.be.deep.equal(['aa', 'ba', 'ca', 'da']); 34 | }); 35 | }); 36 | 37 | describe('When called on empty source', () => { 38 | it('Should return empty source', async () => { 39 | const source = []; 40 | const iterator = map(fromArray(source), (elem, idx) => elem + idx + 1234); 41 | 42 | expect(await toArray(iterator)).to.be.deep.equal([]); 43 | }); 44 | }); 45 | 46 | describe('When applying index transformation', () => { 47 | it('Should return the array indexes', async () => { 48 | const source = [10, 1, 1, 2, 3]; 49 | const iterator = map(fromArray(source), (_, idx) => idx); 50 | 51 | expect(await toArray(iterator)).to.be.deep.equal([0, 1, 2, 3, 4]); 52 | }); 53 | }); 54 | 55 | describe('When applying index and value transformation', () => { 56 | it('Should return the index+value array', async () => { 57 | const source = [2, 3, 4, 3, 2]; 58 | const iterator = map(fromArray(source), (elem, idx) => idx + elem); 59 | 60 | expect(await toArray(iterator)).to.be.deep.equal([2, 4, 6, 6, 6]); 61 | }); 62 | 63 | it('Should return the object.property + 2*index', async () => { 64 | const source = [{ karma: 2 }, { karma: 4 }]; 65 | const iterator = map(fromArray(source), (elem, idx) => elem.karma + 2 * idx); 66 | 67 | expect(await toArray(iterator)).to.be.deep.equal([2, 6]); 68 | }); 69 | }); 70 | 71 | describe('When calling on some Promises', () => { 72 | it('Should return rejected Promise', async () => { 73 | // hitting all tests (coverage) 74 | const source = [ 75 | Promise.resolve(1).finally(), 76 | Promise.reject(1).finally(), 77 | Promise.resolve(1), 78 | ]; 79 | const q = map(fromArray(source), x => x); 80 | 81 | expect(toArray(q)).to.eventually.be.rejected; 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /test/iterators/skip.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { skip } from '../../lib/iterators/skip'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | import { toArray } from '../helpers/toArray'; 5 | 6 | describe('iterators/skip', () => { 7 | describe('When called on empty array', () => { 8 | it('Should return empty array', async () => { 9 | const source = []; 10 | const iterator = skip(fromArray(source), 1); 11 | 12 | expect(await toArray(iterator)).to.be.deep.equal([]); 13 | }); 14 | }); 15 | 16 | describe('When skipping all elements', () => { 17 | it('Should return empty array', async () => { 18 | const source = [1, 2, 4, 8, 16, 32, 64]; 19 | const iterator = skip(fromArray(source), 100); 20 | 21 | expect(await toArray(iterator)).to.be.deep.equal([]); 22 | }); 23 | }); 24 | 25 | describe('When skipping some elements', () => { 26 | it('Should return the remaining array', async () => { 27 | const source = [1, 2, 4, 8, 16]; 28 | const iterator = skip(fromArray(source), 2); 29 | 30 | expect(await toArray(iterator)).to.be.deep.equal([4, 8, 16]); 31 | }); 32 | it('Should return the remaining array', async () => { 33 | const source = [1, 2, 4, 8]; 34 | const iterator = skip(fromArray(source), 0); 35 | 36 | expect(await toArray(iterator)).to.be.deep.equal([1, 2, 4, 8]); 37 | }); 38 | it('Should return the remaining array', async () => { 39 | const source = [1, 2, 4, 8, 16, 32]; 40 | const iterator = skip(fromArray(source), 4); 41 | 42 | expect(await toArray(iterator)).to.be.deep.equal([16, 32]); 43 | }); 44 | }); 45 | 46 | describe('When skipping negative count', () => { 47 | it('Should throw error', async () => { 48 | const source = []; 49 | 50 | expect(() => skip(fromArray(source), -1)).to.throw(Error); 51 | }); 52 | }); 53 | 54 | describe('When calling on some Promises', () => { 55 | it('Should return rejected Promise', async () => { 56 | // hitting all tests (coverage) 57 | const source = [ 58 | Promise.resolve(1).finally(), 59 | Promise.reject(1).finally(), 60 | Promise.resolve(1), 61 | ]; 62 | const q = skip(fromArray(source), 1); 63 | 64 | expect(toArray(q)).to.eventually.be.rejected; 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/iterators/slice.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { slice } from '../../lib/iterators/slice'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | import { asyncIterator } from '../../lib/utils/asyncIterator'; 5 | import { toArray } from '../helpers/toArray'; 6 | 7 | describe('iterators/slice', () => { 8 | describe('when called multiple times', () => { 9 | it('Should return new iterator on each call', async () => { 10 | const source = slice(fromArray([1, 2]), 0, 1); 11 | 12 | expect(asyncIterator(source)).not.equals(asyncIterator(source)); 13 | }); 14 | }); 15 | 16 | describe('When called without start and end', () => { 17 | it('Should return the same source', async () => { 18 | const source = [1, 2, 3, 1]; 19 | const it = slice(fromArray(source)); 20 | 21 | expect(await toArray(it)).to.be.deep.equal([1, 2, 3, 1]); 22 | }); 23 | }); 24 | 25 | describe('When start is negative', () => { 26 | it('Should throw error', async () => { 27 | const source = [4, 5, 3, 1, 2]; 28 | 29 | expect(() => toArray(slice(fromArray(source), -10))).to.throw(Error); 30 | }); 31 | }); 32 | 33 | describe('When start is undefined and end is provided', () => { 34 | it('Should return array of 3 elements', async () => { 35 | const source = [4, 5, 3, 2, 5, 4, 3]; 36 | const iter = slice(fromArray(source), undefined, 4); 37 | 38 | expect(await toArray(iter)).to.be.deep.equal([4, 5, 3, 2]); 39 | }); 40 | }); 41 | 42 | describe('When end is negative', () => { 43 | it('Should throw exception', async () => { 44 | const source = []; 45 | 46 | expect(() => toArray(slice(fromArray(source), 0, -1))).to.throw(Error); 47 | }); 48 | }); 49 | 50 | describe('When called with start only', () => { 51 | it('Should return array of 3 elements', async () => { 52 | const source = [1, 3, 4, 2]; 53 | const iter = slice(fromArray(source), 1); 54 | 55 | expect(await toArray(iter)).to.be.deep.equal([3, 4, 2]); 56 | }); 57 | }); 58 | 59 | describe('When called with start and end', () => { 60 | it('Should return array of 2 elements', async () => { 61 | const source = [1, 3, 4, 2, 1]; 62 | const iter = slice(fromArray(source), 1, 3); 63 | 64 | expect(await toArray(iter)).to.be.deep.equal([3, 4]); 65 | }); 66 | }); 67 | 68 | describe('When calling on some Promises', () => { 69 | it('Should return rejected Promise', async () => { 70 | // hitting all tests (coverage) 71 | const source = [ 72 | Promise.resolve(1).finally(), 73 | Promise.reject(1).finally(), 74 | Promise.resolve(1), 75 | ]; 76 | const q = slice(fromArray(source), 0, 1); 77 | 78 | expect(toArray(q)).to.eventually.be.rejected; 79 | }); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /test/iterators/take.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { take } from '../../lib/iterators/take'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | import { toArray } from '../helpers/toArray'; 5 | 6 | describe('iterators/take', () => { 7 | describe('When take 0 elements', () => { 8 | it('Should return empty source', async () => { 9 | const source = [1, 2]; 10 | const iterator = take(fromArray(source), 0); 11 | 12 | expect(await toArray(iterator)).to.deep.equal([]); 13 | }); 14 | }); 15 | 16 | describe('When take some elements', () => { 17 | it('Should return 4 elements from front', async () => { 18 | const source = [1, 2, 3, 4, 5, 6]; 19 | const iterator = take(fromArray(source), 4); 20 | 21 | expect(await toArray(iterator)).to.deep.equal([1, 2, 3, 4]); 22 | }); 23 | it('Should return 2 elements from front', async () => { 24 | const source = [1, 2, 3, 4, 5]; 25 | const iterator = take(fromArray(source), 2); 26 | 27 | expect(await toArray(iterator)).to.deep.equal([1, 2]); 28 | }); 29 | it('Should return 1 element from front', async () => { 30 | const source = [1, 2, 3]; 31 | const iterator = take(fromArray(source), 1); 32 | 33 | expect(await toArray(iterator)).to.deep.equal([1]); 34 | }); 35 | }); 36 | 37 | describe('When take all elements', () => { 38 | it('Should return all elements', async () => { 39 | const source = [1, 2, 3, 4, 5, 6]; 40 | const iterator = take(fromArray(source), 6); 41 | 42 | expect(await toArray(iterator)).to.deep.equal([1, 2, 3, 4, 5, 6]); 43 | }); 44 | }); 45 | 46 | describe('When take more than all elements', () => { 47 | it('Should return all elements', async () => { 48 | const source = [1, 2, 3]; 49 | const iterator = take(fromArray(source), 10); 50 | 51 | expect(await toArray(iterator)).to.deep.equal([1, 2, 3]); 52 | }); 53 | }); 54 | 55 | describe('When take negative count', () => { 56 | it('Should return 3 elements from tail', async () => { 57 | const source = [1, 2, 3, 4]; 58 | 59 | expect(() => take(fromArray(source), -3)).to.throw(Error); 60 | }); 61 | }); 62 | 63 | describe('When take positive count from empty source', () => { 64 | it('Should return empty source', async () => { 65 | const source = []; 66 | const iterator = take(fromArray(source), 8); 67 | 68 | expect(await toArray(iterator)).to.deep.equal([]); 69 | }); 70 | }); 71 | 72 | describe('When calling on some Promises', () => { 73 | it('Should return rejected Promise', async () => { 74 | // hitting all tests (coverage) 75 | const source = [ 76 | Promise.resolve(1).finally(), 77 | Promise.reject(1).finally(), 78 | Promise.resolve(1), 79 | ]; 80 | const q = take(fromArray(source), 1); 81 | 82 | expect(toArray(q)).to.eventually.be.rejected; 83 | }); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /test/itiriri/filter.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { default as itiririAsync } from '../../lib'; 3 | import { fromArray, numbers as numberGenerator } from '../helpers/asyncGenerators'; 4 | import { SpyAsyncIterable } from '../helpers/SpyAsyncIterable'; 5 | import { toArray } from '../helpers/toArray'; 6 | 7 | describe('ItiririAsync (filter)', () => { 8 | describe('When calling filter', () => { 9 | it('Should be a deferred method', async () => { 10 | const source = new SpyAsyncIterable(numberGenerator()); 11 | itiririAsync(source).filter(() => true); 12 | 13 | expect(source.wasIterated).to.be.false; 14 | }); 15 | 16 | it('Should return array of 3 elements', async () => { 17 | const source = [0, -4, 4, 30, -10, 10]; 18 | const q = itiririAsync(fromArray(source)).filter(x => x <= 0); 19 | 20 | expect(await toArray(q)).to.be.deep.equal([0, -4, -10]); 21 | }); 22 | 23 | it('Should return array of 1 element', async () => { 24 | const source = [0, -4, 4, 30, -10, 10]; 25 | const q = itiririAsync(fromArray(source)).filter((_, idx) => idx === 0); 26 | 27 | expect(await toArray(q)).to.be.deep.equal([0]); 28 | }); 29 | 30 | it('Should return array of 1 object', async () => { 31 | const source = [ 32 | { val: 10, tag: 'a' }, 33 | { val: 20, tag: 'b' }, 34 | { val: -10, tag: 'c' }, 35 | ]; 36 | const q = itiririAsync(fromArray(source)).filter(x => x.tag === 'a'); 37 | 38 | expect(await toArray(q)).to.be.deep.equal([{ val: 10, tag: 'a' }]); 39 | }); 40 | }); 41 | 42 | describe('When calling skip', () => { 43 | it('Should be a deferred method', async () => { 44 | const source = new SpyAsyncIterable(numberGenerator()); 45 | itiririAsync(fromArray(source)).skip(1000); 46 | 47 | expect(source.wasIterated).to.be.false; 48 | }); 49 | 50 | it('Should return 5 elements', async () => { 51 | const source = numberGenerator(); 52 | const q = itiririAsync(source).skip(2).take(5); 53 | 54 | expect(await toArray(q)).to.be.deep.equal([2, 3, 4, 5, 6]); 55 | }); 56 | }); 57 | 58 | describe('When calling slice', () => { 59 | it('Should be a deferred method', async () => { 60 | const source = new SpyAsyncIterable(numberGenerator()); 61 | itiririAsync(source).slice(100, 2000); 62 | 63 | expect(source.wasIterated).to.be.false; 64 | }); 65 | 66 | it('Should return 3 elements', async () => { 67 | const source = numberGenerator(1, 2); 68 | const q = itiririAsync(source).slice(4, 6); 69 | 70 | expect(await toArray(q)).to.be.deep.equal([9, 11]); 71 | }); 72 | 73 | it('Should return empty source', async () => { 74 | const source = numberGenerator(10, 2); 75 | const q = itiririAsync(source).slice(7, 6); 76 | 77 | expect(await toArray(q)).to.be.deep.equal([]); 78 | }); 79 | 80 | it('Should return no elements', async () => { 81 | const source = numberGenerator(10, 2); 82 | const q = itiririAsync(source).slice(0, 0); 83 | 84 | expect(await toArray(q)).to.be.deep.equal([]); 85 | }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /test/itiriri/join.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { default as itiririAsync } from '../../lib'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | import { SpyAsyncIterable } from '../helpers/SpyAsyncIterable'; 5 | import { SpyIterable } from '../helpers/SpyIterable'; 6 | import { toArray } from '../helpers/toArray'; 7 | 8 | describe('ItiririAsync (join)', () => { 9 | describe('When calling join', () => { 10 | it('Should be a deferred method', async () => { 11 | const source1 = new SpyAsyncIterable(fromArray([])); 12 | const source2 = new SpyIterable([]); 13 | await itiririAsync(source1).join(source2, x => x, x => x, x => x); 14 | 15 | expect(source1.wasIterated).to.be.false; 16 | expect(source2.wasIterated).to.be.false; 17 | }); 18 | 19 | it('Should return array of 2 elements', async () => { 20 | const source1 = [0, 4, 5, 1]; 21 | const source2 = [-1, 4, 5, -1]; 22 | const q = itiririAsync(fromArray(source1)).join(source2, x => x, x => x, x => x); 23 | 24 | expect(await toArray(q)).to.deep.equal([4, 5]); 25 | }); 26 | 27 | it('Should return array of 3 elements', async () => { 28 | const source1 = [ 29 | { val: 1, tag: 'a' }, 30 | { val: 11, tag: 'b' }, 31 | { val: 111, tag: 'a' }, 32 | { val: 1111, tag: 'c' }, 33 | ]; 34 | const source2 = [ 35 | { val: 2, tag: 'a' }, 36 | { val: 2222, tag: 'c' }, 37 | ]; 38 | const q = itiririAsync(fromArray(source1)).join( 39 | source2, 40 | x => x.tag, 41 | x => x.tag, 42 | (e1, e2) => e1.val + e2.val, 43 | ); 44 | 45 | expect(await toArray(q)).to.deep.equal([3, 113, 3333]); 46 | }); 47 | }); 48 | 49 | describe('When calling leftJoin', () => { 50 | it('Should be a deferred method', async () => { 51 | const source1 = new SpyAsyncIterable(fromArray([])); 52 | const source2 = new SpyIterable([]); 53 | await itiririAsync(source1).leftJoin(source2, x => x, x => x, x => x); 54 | 55 | expect(source1.wasIterated).to.be.false; 56 | expect(source2.wasIterated).to.be.false; 57 | }); 58 | 59 | it('Should return array of 4 elements', async () => { 60 | const source1 = [0, 4, 5, 1]; 61 | const source2 = [-1, 4, 5, -1]; 62 | const q = itiririAsync(fromArray(source1)).leftJoin( 63 | source2, 64 | x => x, 65 | x => x, 66 | (e1, e2) => ({ e1, e2 }), 67 | ); 68 | 69 | expect(await toArray(q)).to.deep.equal([ 70 | { e1: 0, e2: undefined }, 71 | { e1: 4, e2: 4 }, 72 | { e1: 5, e2: 5 }, 73 | { e1: 1, e2: undefined }, 74 | ]); 75 | }); 76 | }); 77 | 78 | describe('When calling groupJoin', () => { 79 | it('Should be a deferred method', async () => { 80 | const source1 = new SpyAsyncIterable(fromArray([])); 81 | const source2 = new SpyIterable([]); 82 | await itiririAsync(source1).groupJoin(source2, x => x, x => x, x => x); 83 | 84 | expect(source1.wasIterated).to.be.false; 85 | expect(source2.wasIterated).to.be.false; 86 | }); 87 | 88 | it('Should return array of 1 elements', async () => { 89 | const source1 = [0, 4, 5, 1]; 90 | const source2 = [-1, 5, 5, 5, 1]; 91 | const q = itiririAsync(fromArray(source1)).groupJoin( 92 | source2, 93 | x => x, 94 | x => x, 95 | (e1, e2) => ({ e1, e2 }), 96 | ); 97 | 98 | expect(await toArray(q)).to.deep.equal([ 99 | { e1: 0, e2: [] }, 100 | { e1: 4, e2: [] }, 101 | { e1: 5, e2: [5, 5, 5] }, 102 | { e1: 1, e2: [1] }, 103 | ]); 104 | }); 105 | }); 106 | }); 107 | -------------------------------------------------------------------------------- /test/itiriri/predicate.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { default as itiririAsync } from '../../lib'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | 5 | describe('ItiririAsync (predicate)', () => { 6 | describe('When calling includes', () => { 7 | it('Should return true on array', async () => { 8 | const source = [0, 4, 4, 30, 10, 10]; 9 | const q = itiririAsync(fromArray(source)); 10 | 11 | expect(await q.includes(4)).to.be.true; 12 | }); 13 | 14 | it('Should return false on empty source', async () => { 15 | const source = []; 16 | const q = itiririAsync(fromArray(source)); 17 | 18 | expect(await q.includes(0)).to.be.false; 19 | }); 20 | 21 | it('Should return false when using fromIndex', async () => { 22 | const source = [1, 2, 3, 4]; 23 | const q = itiririAsync(fromArray(source)); 24 | 25 | expect(await q.includes(1, 1)).to.be.false; 26 | }); 27 | }); 28 | 29 | describe('When calling every', () => { 30 | it('Should return true on array', async () => { 31 | const source = [0, 4, 4, 30, 10, 10]; 32 | const q = itiririAsync(fromArray(source)); 33 | 34 | expect(await q.every(x => x >= 0)).to.be.true; 35 | }); 36 | 37 | it('Should return true on empty source', async () => { 38 | const source = []; 39 | const q = itiririAsync(fromArray(source)); 40 | 41 | expect(await q.every(x => x * 20 === 0)).to.be.true; 42 | }); 43 | 44 | it('Should return false on array of objects', async () => { 45 | const source = [ 46 | { val: 10, tag: 'a' }, 47 | { val: 20, tag: 'b' }, 48 | { val: -10, tag: 'c' }, 49 | ]; 50 | const q = itiririAsync(fromArray(source)); 51 | 52 | expect(await q.every(x => x.val <= 10)).to.be.false; 53 | }); 54 | 55 | it('Should return true on array of objects', async () => { 56 | const source = [ 57 | { val: 10, tag: 'a' }, 58 | { val: 20, tag: 'b' }, 59 | { val: -10, tag: 'c' }, 60 | ]; 61 | const q = itiririAsync(fromArray(source)); 62 | 63 | expect(await q.every((_, idx) => idx < 10)).to.be.true; 64 | }); 65 | }); 66 | 67 | describe('When calling some', () => { 68 | it('Should return true on array', async () => { 69 | const source = [0, 4, 4, 30, 10, 10]; 70 | const q = itiririAsync(fromArray(source)); 71 | 72 | expect(await q.some(x => x >= 30)).to.be.true; 73 | }); 74 | 75 | it('Should return true on empty source', async () => { 76 | const source = []; 77 | const q = itiririAsync(fromArray(source)); 78 | 79 | expect(await q.some(x => x * 20 === 0)).to.be.false; 80 | }); 81 | 82 | it('Should return false on array of objects', async () => { 83 | const source = [ 84 | { val: 10, tag: 'a' }, 85 | { val: 20, tag: 'b' }, 86 | { val: -10, tag: 'c' }, 87 | ]; 88 | const q = itiririAsync(fromArray(source)); 89 | 90 | expect(await q.some(x => x.val < -10)).to.be.false; 91 | }); 92 | 93 | it('Should return true on array of objects', async () => { 94 | const source = [ 95 | { val: 10, tag: 'a' }, 96 | { val: 20, tag: 'b' }, 97 | { val: -10, tag: 'c' }, 98 | ]; 99 | const q = itiririAsync(fromArray(source)); 100 | 101 | expect(await q.some((_, idx) => idx < 10)).to.be.true; 102 | }); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /test/itiriri/query.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { default as itiririAsync } from '../../lib'; 3 | import { asyncIterable } from '../../lib/utils/asyncIterable'; 4 | import { fromArray, numbers as numberGenerator } from '../helpers/asyncGenerators'; 5 | import { SpyAsyncIterable } from '../helpers/SpyAsyncIterable'; 6 | import { toArray } from '../helpers/toArray'; 7 | 8 | describe('ItiririAsync (query)', () => { 9 | describe('When calling entries', () => { 10 | it('Should be a deferred method', async () => { 11 | const source = new SpyAsyncIterable(numberGenerator()); 12 | await itiririAsync(source).entries(); 13 | 14 | expect(source.wasIterated).to.be.false; 15 | }); 16 | 17 | it('Should return 4 key/value pairs', async () => { 18 | const source = numberGenerator(0, 2); 19 | const q = itiririAsync(source).take(4).entries(); 20 | 21 | expect(await toArray(q)).to.be.deep.equal([[0, 0], [1, 2], [2, 4], [3, 6]]); 22 | }); 23 | }); 24 | 25 | describe('When calling keys', () => { 26 | it('Should be a deferred method', async () => { 27 | const source = new SpyAsyncIterable(numberGenerator()); 28 | await itiririAsync(source).keys(); 29 | 30 | expect(source.wasIterated).to.be.false; 31 | }); 32 | 33 | it('Should return the keys', async () => { 34 | const source = numberGenerator(); 35 | const q = itiririAsync(source).take(10).keys(); 36 | 37 | expect(await toArray(q)).to.be.deep.equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); 38 | }); 39 | }); 40 | 41 | describe('When calling values', () => { 42 | it('Should be a deferred method', async () => { 43 | const source = new SpyAsyncIterable(numberGenerator()); 44 | await itiririAsync(source).values(); 45 | 46 | expect(source.wasIterated).to.be.false; 47 | }); 48 | 49 | it('Should return a new query with same values', async () => { 50 | const source = [1, 3, 4, 2, 2]; 51 | const q1 = itiririAsync(asyncIterable(() => fromArray(source))); 52 | const q2 = q1.values(); 53 | 54 | expect(q2).to.not.be.equal(q1); 55 | const [res1, res2] = await Promise.all([toArray(q1), toArray(q2)]); 56 | expect(res2).to.be.deep.equal(res1); 57 | }); 58 | }); 59 | 60 | describe('When calling concat', () => { 61 | it('Should be a deferred method', async () => { 62 | const source1 = new SpyAsyncIterable(numberGenerator()); 63 | const source2 = new SpyAsyncIterable(numberGenerator()); 64 | await itiririAsync(source1).take(10).concat(itiririAsync(source2).take(5)); 65 | 66 | expect(source1.wasIterated).to.be.false; 67 | expect(source2.wasIterated).to.be.false; 68 | }); 69 | 70 | it('Should return 10 elements', async () => { 71 | const source1 = numberGenerator(0, 2); 72 | const source2 = numberGenerator(); 73 | const q1 = itiririAsync(source1); 74 | const q2 = itiririAsync(source2).take(5).concat(q1.take(5)); 75 | 76 | expect(await toArray(q2)).to.be.deep.equal([0, 1, 2, 3, 4, 0, 2, 4, 6, 8]); 77 | }); 78 | 79 | it('Should return 2 elements', async () => { 80 | const source = numberGenerator(0, 2); 81 | const q = itiririAsync(source).take(1).concat(Promise.resolve(5)); 82 | 83 | expect(await toArray(q)).to.be.deep.equal([0, 5]); 84 | }); 85 | 86 | it('Should return 4 elements', async () => { 87 | const source = numberGenerator(0, 10); 88 | const q = itiririAsync(source).take(3).concat([1]); 89 | 90 | expect(await toArray(q)).to.be.deep.equal([0, 10, 20, 1]); 91 | }); 92 | }); 93 | 94 | describe('When calling prepend', () => { 95 | it('Should be a deferred method', async () => { 96 | const source1 = new SpyAsyncIterable(fromArray([1, 2, 3])); 97 | const source2 = new SpyAsyncIterable(fromArray([])); 98 | itiririAsync(source1).prepend(source2); 99 | 100 | expect(source1.wasIterated).to.be.false; 101 | expect(source2.wasIterated).to.be.false; 102 | }); 103 | 104 | it('Should return 6 elemnts', async () => { 105 | const source1 = numberGenerator(0, 10); 106 | const source2 = numberGenerator(100, 100); 107 | const q1 = itiririAsync(source1).skip(2).take(3); 108 | const q2 = itiririAsync(source2).prepend(q1).take(6); 109 | 110 | expect(await toArray(q2)).to.be.deep.equal([20, 30, 40, 100, 200, 300]); 111 | }); 112 | 113 | it('Should return 2 elements', async () => { 114 | const source = [1]; 115 | const q = itiririAsync(fromArray(source)).prepend(2); 116 | 117 | expect(await toArray(q)).to.be.deep.equal([2, 1]); 118 | }); 119 | 120 | it('Should return 4 elements', async () => { 121 | const source = numberGenerator(0, 10); 122 | const q = itiririAsync(source).take(3).prepend([1]); 123 | 124 | expect(await toArray(q)).to.be.deep.equal([1, 0, 10, 20]); 125 | }); 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /test/itiriri/set.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { default as itiririAsync } from '../../lib'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | import { SpyAsyncIterable } from '../helpers/SpyAsyncIterable'; 5 | import { SpyIterable } from '../helpers/SpyIterable'; 6 | import { toArray } from '../helpers/toArray'; 7 | 8 | describe('ItiririAsync (set)', () => { 9 | describe('When calling distinct', () => { 10 | it('Should be a deferred method', async () => { 11 | const source = new SpyAsyncIterable(fromArray([0, 1, 2, 2, 1])); 12 | await itiririAsync(source).distinct(x => x); 13 | 14 | expect(source.wasIterated).to.be.false; 15 | }); 16 | 17 | it('Should return array of 2 elements', async () => { 18 | const source = [0, 4, 4, 0]; 19 | const q = itiririAsync(fromArray(source)).distinct(x => x); 20 | 21 | expect(await toArray(q)).to.be.deep.equal([0, 4]); 22 | }); 23 | 24 | it('Should return array of 3 elements', async () => { 25 | const source = [ 26 | { val: 1, tag: 'a' }, 27 | { val: 2, tag: 'b' }, 28 | { val: 3, tag: 'a' }, 29 | { val: 4, tag: 'a' }, 30 | { val: 5, tag: 'b' }, 31 | { val: 6, tag: 'c' }, 32 | ]; 33 | const q = itiririAsync(fromArray(source)).distinct(x => x.tag); 34 | 35 | expect(await toArray(q)).to.be.deep.equal([ 36 | { val: 1, tag: 'a' }, 37 | { val: 2, tag: 'b' }, 38 | { val: 6, tag: 'c' }, 39 | ]); 40 | }); 41 | }); 42 | 43 | describe('When calling exclude', () => { 44 | it('Should be a deferred method', async () => { 45 | const source1 = new SpyIterable([0, 1, 2, 2, 1]); 46 | const source2 = new SpyIterable([0, 1]); 47 | await itiririAsync(fromArray(source1)).exclude(source2, x => x); 48 | 49 | expect(source1.wasIterated).to.be.false; 50 | expect(source2.wasIterated).to.be.false; 51 | }); 52 | 53 | it('Should return array of 2 elements', async () => { 54 | const source1 = [0, 4, 4, 0]; 55 | const source2 = [0, 5]; 56 | const q = itiririAsync(fromArray(source1)).exclude(source2, x => x); 57 | 58 | expect(await toArray(q)).to.be.deep.equal([4, 4]); 59 | }); 60 | 61 | it('Should return array of 3 elements', async () => { 62 | const source1 = [ 63 | { val: 1, tag: 'a' }, 64 | { val: 2, tag: 'b' }, 65 | { val: 3, tag: 'a' }, 66 | { val: 4, tag: 'a' }, 67 | { val: 5, tag: 'b' }, 68 | { val: 6, tag: 'c' }, 69 | ]; 70 | const source2 = [{ val: 10, tag: 'a' }]; 71 | const q = itiririAsync(fromArray(source1)).exclude(source2, x => x.tag); 72 | 73 | expect(await toArray(q)).to.be.deep.equal([ 74 | { val: 2, tag: 'b' }, 75 | { val: 5, tag: 'b' }, 76 | { val: 6, tag: 'c' }, 77 | ]); 78 | }); 79 | }); 80 | 81 | describe('When calling intersect', () => { 82 | it('Should be a deferred method', async () => { 83 | const source1 = new SpyAsyncIterable(fromArray([0, 1, 2, 2, 1])); 84 | const source2 = new SpyIterable([0, 1]); 85 | await itiririAsync(source1).intersect(source2, x => x); 86 | 87 | expect(source1.wasIterated).to.be.false; 88 | expect(source2.wasIterated).to.be.false; 89 | }); 90 | 91 | it('Should return array of 2 elements', async () => { 92 | const source1 = [0, 4, 4, 0, 1]; 93 | const source2 = [0, 5, 4]; 94 | const q = itiririAsync(fromArray(source1)).intersect(source2, x => x); 95 | 96 | expect(await toArray(q)).to.be.deep.equal([0, 4]); 97 | }); 98 | 99 | it('Should return array of 1 elements', async () => { 100 | const source1 = [ 101 | { val: 1, tag: 'a' }, 102 | { val: 2, tag: 'b' }, 103 | { val: 3, tag: 'a' }, 104 | { val: 4, tag: 'a' }, 105 | { val: 5, tag: 'b' }, 106 | { val: 6, tag: 'c' }, 107 | ]; 108 | const source2 = [{ val: 10, tag: 'a' }]; 109 | const q = itiririAsync(fromArray(source1)).intersect(source2, x => x.tag); 110 | 111 | expect(await toArray(q)).to.be.deep.equal([{ val: 1, tag: 'a' }]); 112 | }); 113 | }); 114 | 115 | describe('When calling union', () => { 116 | it('Should be a deferred method', async () => { 117 | const source1 = new SpyAsyncIterable(fromArray([0, 1, 2, 2, 1])); 118 | const source2 = new SpyAsyncIterable(fromArray([0, 1])); 119 | await itiririAsync(source1).union(source2, x => x); 120 | 121 | expect(source1.wasIterated).to.be.false; 122 | expect(source2.wasIterated).to.be.false; 123 | }); 124 | 125 | it('Should return array of 4 elements', async () => { 126 | const source1 = [0, 4, 4, 0, 1]; 127 | const source2 = [0, 5, 4]; 128 | const q = itiririAsync(fromArray(source1)).union(fromArray(source2), x => x); 129 | 130 | expect(await toArray(q)).to.be.deep.equal([0, 4, 1, 5]); 131 | }); 132 | 133 | it('Should return array of 3 elements', async () => { 134 | const source1 = [ 135 | { val: 1, tag: 'a' }, 136 | { val: 11, tag: 'b' }, 137 | { val: 111, tag: 'a' }, 138 | { val: 1111, tag: 'c' }, 139 | ]; 140 | const source2 = [{ val: 10, tag: 'a' }]; 141 | const q = itiririAsync(fromArray(source1)).union(fromArray(source2), x => x.tag); 142 | 143 | expect(await toArray(q)).to.be.deep.equal([ 144 | { val: 1, tag: 'a' }, 145 | { val: 11, tag: 'b' }, 146 | { val: 1111, tag: 'c' }, 147 | ]); 148 | }); 149 | }); 150 | }); 151 | -------------------------------------------------------------------------------- /test/itiriri/transformation.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { default as itiririAsync } from '../../lib'; 3 | import { asyncIterable } from '../../lib/utils/asyncIterable'; 4 | import { fromArray } from '../helpers/asyncGenerators'; 5 | import { SpyAsyncIterable } from '../helpers/SpyAsyncIterable'; 6 | import { toArray } from '../helpers/toArray'; 7 | 8 | describe('ItiririAsync (transformation)', () => { 9 | describe('When calling map', () => { 10 | it('Should be a deferred method', async () => { 11 | const source = new SpyAsyncIterable(fromArray([])); 12 | await itiririAsync(source).map(x => x); 13 | 14 | expect(source.wasIterated).to.be.false; 15 | }); 16 | 17 | it('Should return array of 3 elements', async () => { 18 | const source = [0, -4, 4]; 19 | const q = itiririAsync(fromArray(source)).map(x => x <= 0); 20 | 21 | expect(await toArray(q)).to.be.deep.equal([true, true, false]); 22 | }); 23 | 24 | it('Should return array of 4 element', async () => { 25 | const source = [0, -4, 4, 30]; 26 | const q = itiririAsync(fromArray(source)).map((elem, idx) => elem + idx); 27 | 28 | expect(await toArray(q)).to.be.deep.equal([0, -3, 6, 33]); 29 | }); 30 | 31 | it('Should return array of 1 object', async () => { 32 | const source = []; 33 | const q = itiririAsync(fromArray(source)).filter(x => x); 34 | 35 | expect(await toArray(q)).to.be.deep.equal([]); 36 | }); 37 | }); 38 | 39 | describe('When calling flat', () => { 40 | it('Should be a deferred method', async () => { 41 | const source = new SpyAsyncIterable(fromArray([])); 42 | await itiririAsync(source).flat(x => x); 43 | 44 | expect(source.wasIterated).to.be.false; 45 | }); 46 | 47 | it('Should return array of 5 elements', async () => { 48 | const source = [[1, 2, 3], [4, 5]]; 49 | const q = itiririAsync(fromArray(source)).flat((elem) => { 50 | const res: number[] = []; 51 | 52 | for (const element of elem) { 53 | res.push(element); 54 | } 55 | 56 | return asyncIterable(async function* () { yield* res; }); 57 | }); 58 | 59 | expect(await toArray(q)).to.be.deep.equal([1, 2, 3, 4, 5]); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/itiriri/value.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { default as itiririAsync } from '../../lib'; 3 | import { fromArray, numbers as numberGenerator } from '../helpers/asyncGenerators'; 4 | 5 | const chai = require('chai'); 6 | chai.use(require('chai-as-promised')); 7 | 8 | describe('ItiririAsync (value)', () => { 9 | describe('When calling nth with positive index', () => { 10 | it('Should return first element', async () => { 11 | const source = numberGenerator(); 12 | const q = itiririAsync(source); 13 | 14 | expect(await q.nth(3)).to.be.equal(3); 15 | }); 16 | }); 17 | 18 | describe('When calling nth with negative index', () => { 19 | it('Should return last element', async () => { 20 | const source = numberGenerator(); 21 | const q = itiririAsync(source).take(100); 22 | 23 | expect(q.nth(-1)).to.eventually.be.rejected; 24 | }); 25 | }); 26 | 27 | describe('When calling indexOf', () => { 28 | it('Should return first element index', async () => { 29 | const source = numberGenerator(0, 3); 30 | const q = itiririAsync(source); 31 | 32 | expect(await q.indexOf(0)).to.be.equal(0); 33 | }); 34 | 35 | it('Should return 5th element index', async () => { 36 | const source = numberGenerator(0, 3); 37 | const q = itiririAsync(source); 38 | 39 | expect(await q.indexOf(12)).to.be.equal(4); 40 | }); 41 | 42 | it('Should return 2nd element index', async () => { 43 | const source = [2, 2, 2, 3, 4]; 44 | const q = itiririAsync(fromArray(source)); 45 | 46 | expect(await q.indexOf(2, 1)).to.be.equal(1); 47 | }); 48 | }); 49 | 50 | describe('When calling lastIndexOf', () => { 51 | it('Should return first element index', async () => { 52 | const source = [1, 3, 4, 33, 2, 4]; 53 | const q = itiririAsync(fromArray(source)); 54 | 55 | expect(await q.lastIndexOf(1)).to.be.equal(0); 56 | }); 57 | 58 | it('Should return last element index', async () => { 59 | const source = [1, 3, 4, 33, 2, 4]; 60 | const q = itiririAsync(fromArray(source)); 61 | 62 | expect(await q.lastIndexOf(4)).to.be.equal(5); 63 | }); 64 | 65 | it('Should return 5th element index', async () => { 66 | const source = [0, 1, 0, 0, 0, 2, 2, 2]; 67 | const q = itiririAsync(fromArray(source)); 68 | 69 | expect(await q.lastIndexOf(0)).to.be.equal(4); 70 | }); 71 | 72 | it('Should retrun -1', async () => { 73 | const source = [1, 1, 2, 3, 4, 1, 4]; 74 | const q = itiririAsync(fromArray(source)); 75 | 76 | expect(await q.lastIndexOf(2, 3)).to.be.equal(-1); 77 | }); 78 | }); 79 | 80 | describe('When calling findIndex', () => { 81 | it('Should return first element index', async () => { 82 | const source = [1, 3, 4, 33, 2, 4]; 83 | const q = itiririAsync(fromArray(source)); 84 | 85 | expect(await q.findIndex(x => x === 1)).to.be.equal(0); 86 | }); 87 | 88 | it('Should return last element index', async () => { 89 | const source = [0, 1, 1, 1, 2, 44]; 90 | const q = itiririAsync(fromArray(source)); 91 | 92 | expect(await q.findIndex(x => x - 10 > 30)).to.be.equal(5); 93 | }); 94 | 95 | it('Should return 5th element index', async () => { 96 | const source = [0, 1, 0, 0, -1, 2, 2, 2]; 97 | const q = itiririAsync(fromArray(source)); 98 | 99 | expect(await q.findIndex(x => x < 0)).to.be.equal(4); 100 | }); 101 | 102 | it('Should return -1', async () => { 103 | const source = [0, 1, 0, 0, 1, 2, 2, 2]; 104 | const q = itiririAsync(fromArray(source)); 105 | 106 | expect(await q.findIndex(x => x < 0)).to.be.equal(-1); 107 | }); 108 | }); 109 | 110 | describe('When calling findLastIndex', () => { 111 | it('Should return first element index', async () => { 112 | const source = [1, 3, 4, 33, 2, 4]; 113 | const q = itiririAsync(fromArray(source)); 114 | 115 | expect(await q.findLastIndex(x => x === 1)).to.be.equal(0); 116 | }); 117 | 118 | it('Should return last element index', async () => { 119 | const source = [100, 1, 1, 1, 2, 44]; 120 | const q = itiririAsync(fromArray(source)); 121 | 122 | expect(await q.findLastIndex(x => x - 10 > 30)).to.be.equal(5); 123 | }); 124 | 125 | it('Should return 5th element index', async () => { 126 | const source = [0, 1, 0, 0, -1, 2, 2, 2]; 127 | const q = itiririAsync(fromArray(source)); 128 | 129 | expect(await q.findLastIndex(x => x < 0)).to.be.equal(4); 130 | }); 131 | 132 | it('Should return -1', async () => { 133 | const source = [0, 1, 0, 0, 1, 2, 2, 2]; 134 | const q = itiririAsync(fromArray(source)); 135 | 136 | expect(await q.findLastIndex(x => x < 0)).to.be.equal(-1); 137 | }); 138 | }); 139 | 140 | describe('When calling length', () => { 141 | it('Should return 6', async () => { 142 | const source = [1, 3, 4, 33, 2, 4]; 143 | const q = itiririAsync(fromArray(source)); 144 | 145 | expect(await q.length()).to.be.equal(6); 146 | }); 147 | 148 | it('Should return 1', async () => { 149 | const source = [1, 3, 4, 33, 2, 4]; 150 | const q = itiririAsync(fromArray(source)); 151 | 152 | expect(await q.length(x => x > 10)).to.be.equal(1); 153 | }); 154 | 155 | it('Should return 3', async () => { 156 | const source = [1, 3, 4, 33, 2, 4]; 157 | const q = itiririAsync(fromArray(source)); 158 | 159 | expect(await q.length((_, idx) => idx > 2)).to.be.equal(3); 160 | }); 161 | }); 162 | 163 | describe('When calling first', () => { 164 | it('Should return 6', async () => { 165 | const source = [6, 3, 4, 33, 2, 4]; 166 | const q = itiririAsync(fromArray(source)); 167 | 168 | expect(await q.first()).to.be.equal(6); 169 | }); 170 | 171 | it('Should return undefined', async () => { 172 | const source = []; 173 | const q = itiririAsync(fromArray(source)); 174 | 175 | expect(await q.first()).to.be.undefined; 176 | }); 177 | 178 | it('Should return 3', async () => { 179 | const source = numberGenerator(3, 0); 180 | const q = itiririAsync(source); 181 | 182 | expect(await q.first()).to.be.equal(3); 183 | }); 184 | }); 185 | 186 | describe('When calling find', () => { 187 | it('Should return 33', async () => { 188 | const source = [6, 3, 4, 33, 2, 4]; 189 | const q = itiririAsync(fromArray(source)); 190 | 191 | expect(await q.find(x => x > 30)).to.be.equal(33); 192 | }); 193 | 194 | it('Should return undefined', async () => { 195 | const source = [1, 2]; 196 | const q = itiririAsync(fromArray(source)); 197 | 198 | expect(await q.find((elem, idx) => elem + idx === 0)).to.be.undefined; 199 | }); 200 | 201 | it('Should return first element', async () => { 202 | const source = numberGenerator(3, 3); 203 | const q = itiririAsync(source).take(10); 204 | 205 | expect(await q.find(x => x === 3)).to.be.equal(3); 206 | }); 207 | 208 | it('Should return 33', async () => { 209 | const source = numberGenerator(3, 3); 210 | const q = itiririAsync(source); 211 | 212 | expect(await q.find((_, idx) => idx === 10)).to.be.equal(33); 213 | }); 214 | }); 215 | 216 | describe('When calling last', () => { 217 | it('Should return 4', async () => { 218 | const source = [6, 3, 4, 33, 2, 4]; 219 | const q = itiririAsync(fromArray(source)); 220 | 221 | expect(await q.last()).to.be.equal(4); 222 | }); 223 | 224 | it('Should return undefined', async () => { 225 | const source = []; 226 | const q = itiririAsync(fromArray(source)); 227 | 228 | expect(await q.last()).to.be.undefined; 229 | }); 230 | }); 231 | 232 | describe('When calling findLast', () => { 233 | it('Should return 33', async () => { 234 | const source = [6, 3, 4, 33, 2, 4]; 235 | const q = itiririAsync(fromArray(source)); 236 | 237 | expect(await q.findLast(x => x > 30)).to.be.equal(33); 238 | }); 239 | 240 | it('Should return undefined', async () => { 241 | const source = [1, 2]; 242 | const q = itiririAsync(fromArray(source)); 243 | 244 | expect(await q.findLast((elem, idx) => elem + idx === 0)).to.be.undefined; 245 | }); 246 | 247 | it('Should return first element', async () => { 248 | const source = [3, 4, 5, 5]; 249 | const q = itiririAsync(fromArray(source)).take(10); 250 | 251 | expect(await q.findLast(x => x === 3)).to.be.equal(3); 252 | }); 253 | }); 254 | 255 | describe('When calling average', () => { 256 | it('Should return 33', async () => { 257 | const source = [66, 0, 33]; 258 | const q = itiririAsync(fromArray(source)); 259 | 260 | expect(await q.average()).to.be.equal(33); 261 | }); 262 | 263 | it('Should return undefined', async () => { 264 | const source = []; 265 | const q = itiririAsync(fromArray(source)); 266 | 267 | expect(await q.average()).to.be.undefined; 268 | }); 269 | 270 | it('Should return 10', async () => { 271 | const source = [ 272 | { val: 10, tag: 'a' }, 273 | { val: 20, tag: 'b' }, 274 | { val: 0, tag: 'c' }, 275 | ]; 276 | const q = itiririAsync(fromArray(source)); 277 | 278 | expect(await q.average(x => x.val)).to.be.equal(10); 279 | }); 280 | }); 281 | 282 | describe('When calling min', () => { 283 | it('Should return -1', async () => { 284 | const source = [-1, 3, 4, 33, -1, 4]; 285 | const q = itiririAsync(fromArray(source)); 286 | 287 | expect(await q.min()).to.be.equal(-1); 288 | }); 289 | 290 | it('Should return undefined', async () => { 291 | const source = []; 292 | const q = itiririAsync(fromArray(source)); 293 | 294 | expect(await q.min()).to.be.undefined; 295 | }); 296 | 297 | it('Should return first element', async () => { 298 | const source = [ 299 | { val: -10, tag: 'a' }, 300 | { val: 20, tag: 'b' }, 301 | { val: 0, tag: 'c' }, 302 | ]; 303 | const q = itiririAsync(fromArray(source)); 304 | 305 | expect(await q.min((e1, e2) => e1.val - e2.val)).to.be.equal(source[0]); 306 | }); 307 | }); 308 | 309 | describe('When calling max', () => { 310 | it('Should return 30', async () => { 311 | const source = [-1, 3, 4, 30, 2, 4]; 312 | const q = itiririAsync(fromArray(source)); 313 | 314 | expect(await q.max()).to.be.equal(30); 315 | }); 316 | 317 | it('Should return undefined', async () => { 318 | const source = []; 319 | const q = itiririAsync(fromArray(source)); 320 | 321 | expect(await q.max()).to.be.undefined; 322 | }); 323 | 324 | it('Should return first element', async () => { 325 | const source = [ 326 | { val: 1010, tag: 'a' }, 327 | { val: 20, tag: 'b' }, 328 | { val: 0, tag: 'c' }, 329 | ]; 330 | const q = itiririAsync(fromArray(source)); 331 | 332 | expect(await q.max((e1, e2) => e1.val - e2.val)).to.be.equal(source[0]); 333 | }); 334 | }); 335 | 336 | describe('When calling sum', () => { 337 | it('Should return 30', async () => { 338 | const source = [0, -4, 4, 30, 10, -10]; 339 | const q = itiririAsync(fromArray(source)); 340 | 341 | expect(await q.sum()).to.be.equal(30); 342 | }); 343 | 344 | it('Should return undefined', async () => { 345 | const source = []; 346 | const q = itiririAsync(fromArray(source)); 347 | 348 | expect(await q.sum()).to.be.undefined; 349 | }); 350 | }); 351 | 352 | describe('When calling reduce', () => { 353 | it('Should return 0', async () => { 354 | const source = [0, -4, 4, 30, 10, -10]; 355 | const q = itiririAsync(fromArray(source)); 356 | 357 | expect(await q.reduce(() => 0, 0)).to.be.equal(0); 358 | }); 359 | 360 | it('Should throw exception', async () => { 361 | const source = []; 362 | const q = itiririAsync(fromArray(source)); 363 | 364 | expect(q.reduce(() => 0)).to.eventually.be.rejected; 365 | }); 366 | 367 | it('Should return 20', async () => { 368 | const source = [ 369 | { val: 10, tag: 'a' }, 370 | { val: 20, tag: 'b' }, 371 | { val: -10, tag: 'c' }, 372 | ]; 373 | const q = itiririAsync(fromArray(source)); 374 | 375 | expect(await q.reduce((x, e) => x + e.val, 0)).to.be.equal(20); 376 | }); 377 | 378 | it('Should return abc', async () => { 379 | const source = [ 380 | { val: 10, tag: 'a' }, 381 | { val: 20, tag: 'b' }, 382 | { val: -10, tag: 'c' }, 383 | ]; 384 | const q = itiririAsync(fromArray(source)); 385 | 386 | expect(await q.reduce((x, e) => x + e.tag, '')).to.be.equal('abc'); 387 | }); 388 | }); 389 | 390 | describe('When calling forEach', () => { 391 | it('Should return 4 transformed elements', async () => { 392 | const source = numberGenerator(); 393 | const result: number[] = []; 394 | await itiririAsync(source).take(4).forEach(elem => result.push(elem + 10)); 395 | 396 | expect(result).to.be.deep.equal([10, 11, 12, 13]); 397 | }); 398 | 399 | it('Should return 3 transformed elements', async () => { 400 | const q = itiririAsync(numberGenerator(10, 10)); 401 | const result: number[] = []; 402 | await q.take(3).forEach((elem, idx) => result.push(elem + idx)); 403 | 404 | expect(result).to.be.deep.equal([ 405 | 10, 21, 32, 406 | ]); 407 | }); 408 | }); 409 | 410 | describe('When calling awaitAll', () => { 411 | it('Should return an Iterable', async () => { 412 | const q = itiririAsync(fromArray([1, 2, 3, 2, 1])); 413 | const q1 = await q.awaitAll(); 414 | const it = q1[Symbol.iterator](); 415 | 416 | expect(it).has.property('next'); 417 | 418 | expect(it.next()).to.has.property('value').that.is.equal(1); 419 | expect(it.next()).to.has.property('value').that.is.equal(2); 420 | expect(it.next()).to.has.property('value').that.is.equal(3); 421 | expect(it.next()).to.has.property('value').that.is.equal(2); 422 | expect(it.next()).to.has.property('value').that.is.equal(1); 423 | expect(it.next()).to.has.property('done').that.is.true; 424 | }); 425 | }); 426 | }); 427 | -------------------------------------------------------------------------------- /test/itiririAsync.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { default as itiririAsync } from '../lib'; 3 | import { fromArray } from './helpers/asyncGenerators'; 4 | import { SpyAsyncIterable } from './helpers/SpyAsyncIterable'; 5 | 6 | describe('ItiririAsync', () => { 7 | describe('When calling constructor', () => { 8 | it('Should return an ItiririAsync object', () => { 9 | const source = []; 10 | const q = itiririAsync(fromArray(source)); 11 | 12 | const methods = [ 13 | 'entries', 'keys', 'values', 'forEach', 'concat', 'prepend', 14 | 'filter', 'take', 15 | 'skip', 'slice', 'join', 'leftJoin', 'groupJoin', 16 | 'every', 'some', 'includes', 17 | 'distinct', 'exclude', 'intersect', 'union', 'map', 'flat', 18 | 'nth', 'indexOf', 'findIndex', 'lastIndexOf', 'findLastIndex', 'length', 19 | 'first', 'find', 'last', 'findLast', 'average', 'min', 20 | 'max', 'sum', 'reduce', 21 | ]; 22 | 23 | methods.forEach((method) => { 24 | expect(q).to.have.property(method); 25 | }); 26 | }); 27 | 28 | it('Iteration should be deferred', async () => { 29 | const source = new SpyAsyncIterable(fromArray([1, 2])); 30 | await itiririAsync(source); 31 | 32 | expect(source.wasIterated).to.be.false; 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/reducers/average.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { average } from '../../lib/reducers/average'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | 5 | describe('reducers/average', () => { 6 | describe('When calling on some integer array', () => { 7 | it('Should return the average of 4 elements', async () => { 8 | const source = [0, 2, 2, 4]; 9 | 10 | expect(await average(fromArray(source))).to.be.equal(2); 11 | }); 12 | 13 | it('Should return the average of 1 element', async () => { 14 | const source = [0]; 15 | 16 | expect(await average(fromArray(source))).to.be.equal(0); 17 | }); 18 | 19 | it('Should return the average of 2 elements', async () => { 20 | const source = [0, -2]; 21 | 22 | expect(await average(fromArray(source))).to.be.equal(-1); 23 | }); 24 | 25 | it('Should return the average of 5 elements', async () => { 26 | const source = [11.1, 2.2, 6.7, 14.5, 15.5]; 27 | 28 | expect(await average(fromArray(source))).to.be.equal(10.0); 29 | }); 30 | }); 31 | 32 | describe('When calling on empty source', () => { 33 | it('Should return undefined', async () => { 34 | const source = []; 35 | 36 | expect(await average(fromArray(source))).to.be.undefined; 37 | }); 38 | }); 39 | 40 | describe('When calling on 3 Promises', () => { 41 | it('Should return rejected Promise', async () => { 42 | // hitting all tests (coverage) 43 | const source = [ 44 | Promise.resolve(1).finally(), 45 | Promise.reject(1).finally(), 46 | Promise.resolve(1), 47 | ]; 48 | const q = average(fromArray(source)); 49 | 50 | expect(q).to.eventually.be.rejected; 51 | }); 52 | }); 53 | 54 | describe('When calling on 2 Promises', () => { 55 | it('Should return rejected Promise', async () => { 56 | // hitting all tests (coverage) 57 | const source = [ 58 | Promise.reject(1).finally(), 59 | Promise.resolve(1), 60 | ]; 61 | const q = average(fromArray(source)); 62 | 63 | expect(q).to.eventually.be.rejected; 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /test/reducers/awaitAll.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { empty, fromArray } from '../helpers/asyncGenerators'; 3 | import { awaitAll } from '../../lib/reducers/awaitAll'; 4 | 5 | describe('reducers/awaitAll', () => { 6 | describe('When accessing the iterator', () => { 7 | it('Should return an Iterator', async () => { 8 | const iterator = await awaitAll(empty()); 9 | 10 | expect(iterator[Symbol.iterator]()).to.have.property('next'); 11 | }); 12 | }); 13 | 14 | describe('When calling on 3 Promises', () => { 15 | it('Should return rejected Promise', async () => { 16 | // hitting all tests (coverage) 17 | const source = [ 18 | Promise.resolve(1).finally(), 19 | Promise.reject(1).finally(), 20 | Promise.resolve(1), 21 | ]; 22 | const q = awaitAll(fromArray(source)); 23 | 24 | expect(q).to.eventually.be.rejected; 25 | }); 26 | }); 27 | 28 | describe('When calling on 2 Promises', () => { 29 | it('Should return rejected Promise', async () => { 30 | // hitting all tests (coverage) 31 | const source = [ 32 | Promise.reject(1).finally(), 33 | Promise.resolve(1), 34 | ]; 35 | const q = awaitAll(fromArray(source)); 36 | 37 | expect(q).to.eventually.be.rejected; 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/reducers/first.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { first } from '../../lib/reducers/first'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | 5 | describe('reducers/first', () => { 6 | describe('When calling on some source', () => { 7 | it('Should return the first element', async () => { 8 | const arr = [1, 2, 3]; 9 | 10 | expect(await first(fromArray(arr))).to.be.equal(1); 11 | }); 12 | 13 | it('Should return the first negative element', async () => { 14 | const arr = [-1, 2, 3]; 15 | 16 | expect(await first(fromArray(arr))).to.be.equal(-1); 17 | }); 18 | 19 | it('Should return the first object element', async () => { 20 | const arr = [{}, 1, 2, 3]; 21 | 22 | expect(await first(fromArray(arr))).to.be.equal(arr[0]); 23 | }); 24 | }); 25 | 26 | describe('When calling on empty source', () => { 27 | it('Should return undefined', async () => { 28 | const arr = []; 29 | 30 | expect(await first(fromArray(arr))).to.be.undefined; 31 | }); 32 | }); 33 | 34 | describe('When calling on 3 Promises', () => { 35 | it('Should return rejected Promise', async () => { 36 | // hitting all tests (coverage) 37 | const source = [ 38 | Promise.reject(1).finally(), 39 | Promise.resolve(1).finally(), 40 | Promise.resolve(1), 41 | ]; 42 | const q = first(fromArray(source)); 43 | 44 | expect(q).to.eventually.be.rejected; 45 | q.catch(val => expect(val).to.be.equal(1)); 46 | }); 47 | }); 48 | 49 | describe('When calling on 2 Promises', () => { 50 | it('Should return rejected Promise', async () => { 51 | // hitting all tests (coverage) 52 | const source = [ 53 | Promise.reject(1).finally(), 54 | Promise.resolve(1), 55 | ]; 56 | const q = first(fromArray(source)); 57 | 58 | expect(q).to.eventually.be.rejected; 59 | q.catch(val => expect(val).to.be.equal(1)); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/reducers/forEach.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { fromArray } from '../helpers/asyncGenerators'; 3 | import { forEach } from '../../lib/reducers/forEach'; 4 | 5 | describe('reducers/forEach', () => { 6 | describe('When calling on some array', () => { 7 | it('Should return 4 transformed elements', async () => { 8 | const source = [4, 5, 3, 2]; 9 | const result: number[] = []; 10 | await forEach(fromArray(source), elem => result.push(elem * 10 + 1)); 11 | 12 | expect(result).to.be.deep.equal([41, 51, 31, 21]); 13 | }); 14 | 15 | it('Should return 3 transformed elements', async () => { 16 | const source = [5, 3, 2]; 17 | const result: number[] = []; 18 | await forEach(fromArray(source), (elem, index) => result.push(elem + index)); 19 | 20 | expect(result).to.be.deep.equal([5, 4, 4]); 21 | }); 22 | }); 23 | 24 | describe('When calling on some Promises', () => { 25 | it('Should return rejected Promise', async () => { 26 | // hitting all tests (coverage) 27 | const source = [ 28 | Promise.resolve(1).finally(), 29 | Promise.reject(1).finally(), 30 | Promise.resolve(1), 31 | ]; 32 | const q = forEach(fromArray(source), x => x); 33 | 34 | expect(q).to.eventually.be.rejected; 35 | }); 36 | }); 37 | 38 | describe('When calling on 2 Promises', () => { 39 | it('Should return rejected Promise', async () => { 40 | // hitting all tests (coverage) 41 | const source = [ 42 | Promise.reject(1).finally(), 43 | Promise.resolve(1), 44 | ]; 45 | const q = forEach(fromArray(source), x => x); 46 | 47 | expect(q).to.eventually.be.rejected; 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/reducers/indexOf.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { indexOf } from '../../lib/reducers/indexOf'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | 5 | describe('reducers/indexOf', () => { 6 | describe('When called on empty array', () => { 7 | it('Should return -1', async () => { 8 | const source = []; 9 | 10 | expect(await indexOf(fromArray(source), () => true)).to.be.equal(-1); 11 | }); 12 | }); 13 | 14 | describe('When called on some array', () => { 15 | it('Should return the index 3', async () => { 16 | const source = [4, 5, 1, 20, 3]; 17 | 18 | expect(await indexOf(fromArray(source), elem => elem === 20)).to.be.equal(3); 19 | }); 20 | 21 | it('Should return the index 0', async () => { 22 | const source = [1, 2, 1, 3, 4]; 23 | 24 | expect(await indexOf(fromArray(source), elem => elem === 1)).to.be.equal(0); 25 | }); 26 | 27 | it('Should return -1 if elements does not exist', async () => { 28 | const source = ['a', 'b', 'z', 'aa', 'abc']; 29 | 30 | expect(await indexOf(fromArray(source), elem => elem === 'c')).to.be.equal(-1); 31 | }); 32 | 33 | it('Should return the index of last element', async () => { 34 | const source = [5, 7, 1, 3]; 35 | 36 | expect(await indexOf(fromArray(source), elem => elem === 3)).to.be.equal(3); 37 | }); 38 | }); 39 | 40 | describe('When called with index depending predicate', () => { 41 | it('Should return first index', async () => { 42 | const source = [1, 4, 3, 2]; 43 | 44 | expect(await indexOf(fromArray(source), (_, idx) => idx === 0)).to.be.equal(0); 45 | }); 46 | 47 | it('Should return last index', async () => { 48 | const source = [1, 4, 3, 2, 5]; 49 | 50 | expect(await indexOf( 51 | fromArray(source), 52 | (_, idx) => { return idx * 2 === 4; }), 53 | ).to.be.equal(2); 54 | }); 55 | 56 | it('Should return the middle index', async () => { 57 | const source = [1, 40, 3, 200, 1001]; 58 | 59 | expect(await indexOf(fromArray(source), (_, idx) => idx === 2)).to.be.equal(2); 60 | }); 61 | }); 62 | 63 | describe('When calling on some Promises', () => { 64 | it('Should return rejected Promise', async () => { 65 | // hitting all tests (coverage) 66 | const source = [ 67 | Promise.resolve(1).finally(), 68 | Promise.reject(10).finally(), 69 | Promise.resolve(1), 70 | ]; 71 | const q = indexOf(fromArray(source), () => false); 72 | 73 | q.catch(val => expect(val).to.be.equal(10)); 74 | expect(q).to.eventually.be.rejected; 75 | }); 76 | }); 77 | 78 | describe('When calling on 2 Promises', () => { 79 | it('Should return rejected Promise', async () => { 80 | // hitting all tests (coverage) 81 | const source = [ 82 | Promise.reject(1).finally(), 83 | Promise.resolve(1), 84 | ]; 85 | const q = indexOf(fromArray(source), x => x); 86 | 87 | expect(q).to.eventually.be.rejected; 88 | q.catch(val => expect(val).to.be.equal(1)); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /test/reducers/last.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { last } from '../../lib/reducers/last'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | 5 | describe('reducers/last', () => { 6 | describe('When called on empty array', () => { 7 | it('Should return undefined', async () => { 8 | const source = []; 9 | 10 | expect(await last(fromArray(source))).to.be.undefined; 11 | }); 12 | }); 13 | 14 | describe('When called on array with one element', () => { 15 | it('Should return the element', async () => { 16 | const source = [101]; 17 | 18 | expect(await last(fromArray(source))).to.be.equal(101); 19 | }); 20 | }); 21 | 22 | describe('When called on array with multiple elements', () => { 23 | it('Should return the last element (4th)', async () => { 24 | const source = [1, 2, 3, 4]; 25 | 26 | expect(await last(fromArray(source))).to.be.equal(4); 27 | }); 28 | }); 29 | 30 | describe('When called on array with multiple elements', () => { 31 | it('Should return the last element (5th)', async () => { 32 | const source = ['a', 'b', 'c', 'd', 'asdf']; 33 | 34 | expect(await last(fromArray(source))).to.be.equal('asdf'); 35 | }); 36 | }); 37 | 38 | describe('When calling on some Promises', () => { 39 | it('Should return rejected Promise', async () => { 40 | // hitting all tests (coverage) 41 | const source = [ 42 | Promise.resolve(1).finally(), 43 | Promise.reject(1).finally(), 44 | Promise.resolve(1), 45 | ]; 46 | const q = last(fromArray(source)); 47 | 48 | expect(q).to.eventually.be.rejected; 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/reducers/lastIndexOf.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { lastIndexOf } from '../../lib/reducers/lastIndexOf'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | 5 | describe('reducers/lastIndexOf', () => { 6 | describe('When called on empty array', () => { 7 | it('Should return -1', async () => { 8 | const source = []; 9 | 10 | expect(await lastIndexOf(fromArray(source), () => true)).to.be.equal(-1); 11 | }); 12 | }); 13 | 14 | describe('When called on some array', () => { 15 | it('Should return the index 3', async () => { 16 | const source = [4, 5, 1, 20, 3]; 17 | 18 | expect(await lastIndexOf(fromArray(source), elem => elem === 20)).to.be.equal(3); 19 | }); 20 | 21 | it('Should return the index 2', async () => { 22 | const source = [1, 2, 1, 3, 4]; 23 | 24 | expect(await lastIndexOf(fromArray(source), elem => elem === 1)).to.be.equal(2); 25 | }); 26 | 27 | it('Should return -1 if elements does not exist', async () => { 28 | const source = ['a', 'b', 'z', 'aa', 'abc']; 29 | expect(await lastIndexOf(fromArray(source), elem => elem === 'c')).to.be.equal(-1); 30 | }); 31 | }); 32 | 33 | describe('When called with index depending predicate', () => { 34 | it('Should return first index', async () => { 35 | const source = [1, 4, 3, 2]; 36 | 37 | expect(await lastIndexOf(fromArray(source), (_, idx) => idx === 0)).to.be.equal(0); 38 | }); 39 | 40 | it('Should return last index', async () => { 41 | const source = [1, 4, 3, 2, 5]; 42 | 43 | expect(await lastIndexOf( 44 | fromArray(source), (_, idx) => { return idx * 2 === 4; }, 45 | )).to.be.equal(2); 46 | }); 47 | 48 | it('Should return last index for multiple matches', async () => { 49 | const source = [1, 4, 3, 2, 5]; 50 | 51 | expect(await lastIndexOf( 52 | fromArray(source), (_, idx) => { return idx % 2 === 0; }), 53 | ).to.be.equal(4); 54 | }); 55 | 56 | it('Should return the middle index', async () => { 57 | const source = [1, 40, 3, 200, 1001]; 58 | 59 | expect(await lastIndexOf(fromArray(source), (_, idx) => idx === 2)).to.be.equal(2); 60 | }); 61 | }); 62 | 63 | describe('When calling on some Promises', () => { 64 | it('Should return rejected Promise', async () => { 65 | // hitting all tests (coverage) 66 | const source = [ 67 | Promise.resolve(1).finally(), 68 | Promise.reject(1).finally(), 69 | Promise.resolve(1), 70 | ]; 71 | const q = lastIndexOf(fromArray(source), x => x); 72 | 73 | expect(q).to.eventually.be.rejected; 74 | }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /test/reducers/length.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { length } from '../../lib/reducers/length'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | 5 | describe('reducers/length', () => { 6 | describe('When called on empty source', () => { 7 | it('Should return 0', async () => { 8 | const source = []; 9 | 10 | expect(await length(fromArray(source))).to.equal(0); 11 | }); 12 | }); 13 | 14 | describe('When called on a non-empty source', () => { 15 | it('Should return length for 4 elements', async () => { 16 | const source = [0, 1, 2, 1]; 17 | 18 | expect(await length(fromArray(source))).to.equal(4); 19 | }); 20 | it('Should return length for 1 element', async () => { 21 | const source = [0]; 22 | 23 | expect(await length(fromArray(source))).to.equal(1); 24 | }); 25 | it('Should return length for 6 elements', async () => { 26 | const source = [0, 1, 1, 1, 0, 0]; 27 | 28 | expect(await length(fromArray(source))).to.equal(6); 29 | }); 30 | }); 31 | 32 | describe('When calling on some Promises', () => { 33 | it('Should return rejected Promise', async () => { 34 | // hitting all tests (coverage) 35 | const source = [ 36 | Promise.resolve(1).finally(), 37 | Promise.reject(1).finally(), 38 | Promise.resolve(1), 39 | ]; 40 | const q = length(fromArray(source)); 41 | 42 | expect(q).to.eventually.be.rejected; 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/reducers/max.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { max } from '../../lib/reducers/max'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | 5 | describe('reducers/max', () => { 6 | describe('When called on empty array', () => { 7 | it('Should return undefined', async () => { 8 | const source = []; 9 | 10 | expect(await max(fromArray(source), (a, b) => a - b)).to.be.undefined; 11 | }); 12 | }); 13 | 14 | describe('When calle on some array', () => { 15 | it('Should return the first element', async () => { 16 | const source = [4, 0, 1, 3]; 17 | 18 | expect(await max(fromArray(source), (a, b) => a - b)).to.be.equal(4); 19 | }); 20 | 21 | it('Should return the last element', async () => { 22 | const source = [-1, 0, 1, 3]; 23 | 24 | expect(await max(fromArray(source), (a, b) => a - b)).to.be.equal(3); 25 | }); 26 | 27 | it('Should return the middle element', async () => { 28 | const source = [-10, -2, -11, -13, -664]; 29 | 30 | expect(await max(fromArray(source), (a, b) => a - b)).to.be.equal(-2); 31 | }); 32 | 33 | it('Should return 10.99', async () => { 34 | const source = [0.1, 9.9, 10.99, 10.1]; 35 | expect(await max(fromArray(source), (a, b) => a - b)).to.be.equal(10.99); 36 | }); 37 | }); 38 | 39 | describe('When calling on some Promises', () => { 40 | it('Should return rejected Promise', async () => { 41 | // hitting all tests (coverage) 42 | const source = [ 43 | Promise.resolve(1).finally(), 44 | Promise.reject(1).finally(), 45 | Promise.resolve(1), 46 | ]; 47 | const q = max(fromArray(source), x => x); 48 | 49 | expect(q).to.eventually.be.rejected; 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/reducers/min.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { min } from '../../lib/reducers/min'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | 5 | describe('reducers/min', () => { 6 | describe('When called on empty array', () => { 7 | it('Should return undefined', async () => { 8 | const source = []; 9 | 10 | expect(await min(fromArray(source), (a, b) => a - b)).to.be.undefined; 11 | }); 12 | }); 13 | 14 | describe('When called on some array', () => { 15 | it('Should return the first element', async () => { 16 | const source = [-1, 10, 17, 3]; 17 | 18 | expect(await min(fromArray(source), (a, b) => a - b)).to.be.equal(-1); 19 | }); 20 | 21 | it('Should return the last element', async () => { 22 | const source = [-10, -2, -11, -13, -664]; 23 | 24 | expect(await min(fromArray(source), (a, b) => a - b)).to.be.equal(-664); 25 | }); 26 | 27 | it('Should return the middle element', async () => { 28 | const source = [0.1, 9.9, -10.99, 10.1]; 29 | 30 | expect(await min(fromArray(source), (a, b) => a - b)).to.be.equal(-10.99); 31 | }); 32 | }); 33 | 34 | describe('When calling on some Promises', () => { 35 | it('Should return rejected Promise', async () => { 36 | // hitting all tests (coverage) 37 | const source = [ 38 | Promise.resolve(1).finally(), 39 | Promise.reject(1).finally(), 40 | Promise.resolve(1), 41 | ]; 42 | const q = min(fromArray(source), x => x); 43 | 44 | expect(q).to.eventually.be.rejected; 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/reducers/nth.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { nth } from '../../lib/reducers/nth'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | 5 | const chai = require('chai'); 6 | chai.use(require('chai-as-promised')); 7 | 8 | describe('reducers/nth', () => { 9 | describe('When accessing an element that exists', () => { 10 | it('Should return the element from first position', async () => { 11 | const source = [1, 2, 3, 4, 5]; 12 | expect(await nth(fromArray(source), 0)).to.be.equal(1); 13 | }); 14 | 15 | it('Should return the element from a middle position', async () => { 16 | const source = [1, 2, 3, 4, 5]; 17 | expect(await nth(fromArray(source), 2)).to.be.equal(3); 18 | }); 19 | 20 | it('Should return the element from last position', async () => { 21 | const source = [1, 2, 3, 4, 5]; 22 | expect(await nth(fromArray(source), 4)).to.be.equal(5); 23 | }); 24 | }); 25 | 26 | describe('When accessing an element that does not exist', () => { 27 | it('Should return undefined (positive index)', async () => { 28 | const source = [1, 2]; 29 | expect(await nth(fromArray(source), 100)).to.be.undefined; 30 | }); 31 | }); 32 | 33 | describe('When accessing negative index element', () => { 34 | it('Should throw error', async () => { 35 | const source = [32, 49, 3, 20]; 36 | 37 | expect(nth(fromArray(source), -1)).to.eventually.be.rejected; 38 | }); 39 | }); 40 | 41 | describe('When calling on some Promises', () => { 42 | it('Should return rejected Promise', async () => { 43 | // hitting all tests (coverage) 44 | const source = [ 45 | Promise.resolve(1).finally(), 46 | Promise.reject(1).finally(), 47 | Promise.resolve(1), 48 | ]; 49 | const q = nth(fromArray(source), 2); 50 | 51 | q.catch(val => expect(val).to.be.equal(1)); 52 | expect(q).to.eventually.be.rejected; 53 | }); 54 | }); 55 | 56 | describe('When calling on 2 Promises', () => { 57 | it('Should return rejected Promise', async () => { 58 | // hitting all tests (coverage) 59 | const source = [ 60 | Promise.reject(1).finally(), 61 | Promise.resolve(1), 62 | ]; 63 | const q = nth(fromArray(source), 1); 64 | 65 | expect(q).to.eventually.be.rejected; 66 | q.catch(val => expect(val).to.be.equal(1)); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /test/reducers/reduce.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { reduce } from '../../lib/reducers/reduce'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | 5 | const chai = require('chai'); 6 | chai.use(require('chai-as-promised')); 7 | 8 | describe('reducers/reduce', () => { 9 | describe('When called without initial value', () => { 10 | it('Should throw on empty source', async () => { 11 | const source = []; 12 | 13 | expect(reduce(fromArray(source), () => 1)) 14 | .to.eventually.be.rejectedWith('Sequence contains no elements.'); 15 | }); 16 | 17 | it('Should calculate the sum of elements', async () => { 18 | const source = [1, 2, 3]; 19 | 20 | expect(await reduce(fromArray(source), (a, b) => a + b)).to.equal(6); 21 | }); 22 | 23 | it('Should calculate the sum of squares', async () => { 24 | const source = [2, 0, 4, 9]; 25 | 26 | // 2 + 0^2 + 4^2 + 9^2 = 201 27 | expect(await reduce(fromArray(source), (a, b) => a + b * b)).to.equal(99); 28 | }); 29 | 30 | it('Should calculate sum of indexes multiplied by 2', async () => { 31 | const source = [5, 7, 1, 9]; 32 | 33 | // 5{initial element} + (1 + 1) * 2 + (2 + 1) * 2 + (3 + 1) * 2 = 23 34 | expect(await reduce(fromArray(source), (a, _, idx) => a + (idx + 1) * 2)).to.equal(23); 35 | }); 36 | }); 37 | 38 | describe('When called with initial value', () => { 39 | it('Should return the initial value on an empty source', async () => { 40 | const source = []; 41 | 42 | expect(await reduce(fromArray(source), () => 1, 42)).to.equal(42); 43 | }); 44 | 45 | it('Should calculate the sum of elements plus 4', async () => { 46 | const source = [1, 2, 3]; 47 | 48 | expect(await reduce(fromArray(source), (a, b) => a + b, 4)).to.equal(10); 49 | }); 50 | 51 | it('Should calculate the sum of squares plus 100', async () => { 52 | const source = [2, 0, 4, 9]; 53 | 54 | // 100 + 2^2 + 0^2 + 4^2 + 9^2 = 201 55 | expect(await reduce(fromArray(source), (a, b) => a + b * b, 100)).to.equal(201); 56 | }); 57 | 58 | it('Should calculate sum of indexes multiplied by 2', async () => { 59 | const source = [5, 7, 1, 9]; 60 | 61 | // 1{initial element} + (0 + 1) * 2 + (1 + 1) * 2 + (2 + 1) * 2 + (3 + 1) * 2 = 21 62 | expect(await reduce(fromArray(source), (a, _, idx) => a + (idx + 1) * 2, 1)).to.equal(21); 63 | }); 64 | }); 65 | 66 | describe('When calling on some Promises', () => { 67 | it('Should return rejected Promise', async () => { 68 | // hitting all tests (coverage) 69 | const source = [ 70 | Promise.resolve(1).finally(), 71 | Promise.reject(1).finally(), 72 | Promise.resolve(1), 73 | ]; 74 | const q = reduce(fromArray(source), x => x); 75 | 76 | expect(q).to.eventually.be.rejected; 77 | }); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/reducers/sum.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { sum } from '../../lib/reducers/sum'; 3 | import { fromArray } from '../helpers/asyncGenerators'; 4 | 5 | describe('reducers/sum', () => { 6 | describe('When called on empty array', () => { 7 | it('Should return undefined', async () => { 8 | const source = []; 9 | 10 | expect(await sum(fromArray(source))).to.be.undefined; 11 | }); 12 | }); 13 | 14 | describe('When called on some array', () => { 15 | it('Should return 3', async () => { 16 | const source = [-1, 0, 1, 3]; 17 | 18 | expect(await sum(fromArray(source))).to.be.equal(3); 19 | }); 20 | 21 | it('Should return -40', async () => { 22 | const source = [-10, -2, -11, -17]; 23 | 24 | expect(await sum(fromArray(source))).to.be.equal(-40); 25 | }); 26 | 27 | it('Should return 100.5', async () => { 28 | const source = [10.1, 15.1, 15.1, 50.1, 10.1]; 29 | expect(await sum(fromArray(source))).to.be.equal(100.5); 30 | }); 31 | }); 32 | 33 | describe('When calling on some Promises', () => { 34 | it('Should return rejected Promise', async () => { 35 | // hitting all tests (coverage) 36 | const source = [ 37 | Promise.resolve(1).finally(), 38 | Promise.reject(1).finally(), 39 | Promise.resolve(1), 40 | ]; 41 | const q = sum(fromArray(source)); 42 | 43 | expect(q).to.eventually.be.rejected; 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/utils/asyncIterator.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { asyncIterator } from '../../lib/utils/asyncIterator'; 3 | import { empty, fromArray } from '../helpers/asyncGenerators'; 4 | 5 | describe('utils/asyncIterator', () => { 6 | describe('When called on empty source', () => { 7 | it('Should return an iterator', async () => { 8 | const it = asyncIterator(empty()); 9 | 10 | expect(it).to.have.property('next'); 11 | }); 12 | 13 | it('Should return completed iterator', async () => { 14 | const it = asyncIterator(empty()); 15 | 16 | expect(await it.next()).to.have.property('done').that.is.true; 17 | }); 18 | 19 | it('Should return undefined next value', async () => { 20 | const it = asyncIterator(empty()); 21 | 22 | expect(await it.next()).to.have.property('value').that.is.undefined; 23 | }); 24 | }); 25 | 26 | describe('When called on source with one element', () => { 27 | it('Should iterate through element', async () => { 28 | const it = asyncIterator(fromArray([13])); 29 | 30 | let current = await it.next(); 31 | expect(current).to.have.property('value').that.is.equal(13); 32 | expect(current).to.have.property('done').that.is.false; 33 | 34 | current = await it.next(); 35 | expect(current).to.have.property('value').that.is.undefined; 36 | expect(current).to.have.property('done').that.is.true; 37 | }); 38 | }); 39 | 40 | describe('When called on array with multiple elements', () => { 41 | it('Should iterate through elements', async () => { 42 | const it = asyncIterator(fromArray([11, -1, 1])); 43 | 44 | let current = await it.next(); 45 | expect(current).to.have.property('value').that.is.equal(11); 46 | expect(current).to.have.property('done').that.is.false; 47 | 48 | current = await it.next(); 49 | expect(current).to.have.property('value').that.is.equal(-1); 50 | expect(current).to.have.property('done').that.is.false; 51 | 52 | current = await it.next(); 53 | expect(current).to.have.property('value').that.is.equal(1); 54 | expect(current).to.have.property('done').that.is.false; 55 | 56 | current = await it.next(); 57 | expect(current).to.have.property('value').that.is.undefined; 58 | expect(current).to.have.property('done').that.is.true; 59 | }); 60 | }); 61 | 62 | describe('When called multiple times', () => { 63 | it('Should return new iterator on each call', async () => { 64 | const iterator1 = asyncIterator(empty()); 65 | const iterator2 = asyncIterator(empty()); 66 | 67 | expect(iterator1).not.equals(iterator2); 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/utils/isAsyncIterable.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { default as itiririAsync } from '../../lib'; 3 | import { isAsyncIterable } from '../../lib/utils/isAsyncIterable'; 4 | import { empty, numbers } from '../helpers/asyncGenerators'; 5 | 6 | describe('utils/isAsyncIterable', () => { 7 | describe('When called on empty source', async () => { 8 | it('Should return true', () => { 9 | expect(isAsyncIterable(empty())).to.be.true; 10 | }); 11 | }); 12 | describe('When called on non-empty source', () => { 13 | it('Should return true on empty string', async () => { 14 | expect(isAsyncIterable(numbers(1, 1))).to.be.true; 15 | }); 16 | }); 17 | describe('When called on ItiririAsync', () => { 18 | it('Should return true', async () => { 19 | expect(isAsyncIterable(itiririAsync(empty()))).to.be.true; 20 | }); 21 | }); 22 | describe('When called on Number', () => { 23 | it('Should return false', async () => { 24 | expect(isAsyncIterable(4)).to.be.false; 25 | }); 26 | }); 27 | describe('When called on Object', () => { 28 | it('Should return false', async () => { 29 | expect(isAsyncIterable({ a: 1, b: 'a' })).to.be.false; 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "es6", 5 | "esnext" 6 | ], 7 | "importHelpers": true, 8 | "module": "commonjs", 9 | "target": "es6", 10 | "moduleResolution": "node", 11 | "emitDecoratorMetadata": false, 12 | "experimentalDecorators": true, 13 | "sourceMap": true, 14 | "rootDir": ".", 15 | "outDir": "build/compiled", 16 | "declaration": true, 17 | "alwaysStrict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "strictNullChecks": true, 21 | "removeComments": true 22 | }, 23 | "exclude": [ 24 | "build", 25 | "node_modules" 26 | ] 27 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint-config-airbnb", 3 | "rules": { 4 | "no-increment-decrement": false, 5 | "function-name": false 6 | } 7 | } --------------------------------------------------------------------------------