{"id":15437638,"url":"https://github.com/blutorange/js-elbe","last_synced_at":"2025-03-28T06:29:39.041Z","repository":{"id":57221392,"uuid":"119920703","full_name":"blutorange/js-elbe","owner":"blutorange","description":"Utility for working with streams (iterables).","archived":false,"fork":false,"pushed_at":"2018-05-26T22:15:50.000Z","size":2079,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-10-18T10:44:54.929Z","etag":null,"topics":["functional-programming","javascript","stream","typescript"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/blutorange.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-02-02T02:27:59.000Z","updated_at":"2023-04-11T15:22:20.000Z","dependencies_parsed_at":"2022-08-29T01:50:56.475Z","dependency_job_id":null,"html_url":"https://github.com/blutorange/js-elbe","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blutorange%2Fjs-elbe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blutorange%2Fjs-elbe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blutorange%2Fjs-elbe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blutorange%2Fjs-elbe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/blutorange","download_url":"https://codeload.github.com/blutorange/js-elbe/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245983844,"owners_count":20704786,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["functional-programming","javascript","stream","typescript"],"created_at":"2024-10-01T18:58:09.118Z","updated_at":"2025-03-28T06:29:39.019Z","avatar_url":"https://github.com/blutorange.png","language":"TypeScript","readme":"Iterators are great, and work well with Sets and Maps, eg. `(new Map()).entries()`.\nUntil I realized you can't really do much with iterators, and having to do manual\niterations all the time is a pain. Methods and their names inspired by JavaScript,\nJava stream API and ruby's enumerables. Minified, transpiled code without browser\npolyfills etc. is 20 KB, and around ~7 KB gzipped.\n\nLet's compare how parsing a set of JSON strings feels like with this library and vanilla JS:\n\n```javascript\nconst input = new Set([\"9\",\"9a\"])\nconst { stream } = require(\"elbe\");\n\n// Doing it with this library, returns [9,0]\nstream(input).try(JSON.parse)\n    .onError(console.error)\n    .orElse(0)\n    .toArray()\n\n// The same with vanilla JS, returns [9,0]\nArray.from(function*(data) {\n    for (let item of data) {\n        try {\n            yield JSON.parse(item)\n        }\n        catch (e) {\n            console.error(e)\n            yield 0\n        }\n    }\n}(input))\n```\n\n# Roadmap\n\n- testing the API in practice, making it easier to use\n\n# Versioning\n\nThis is currently in version `0.x.y`. Increase in patch version (y) indicates\nbackwards-compatible change, change in minor version non-compatible changes.\n\n# Docs\n\n[All methods with documentation](https://blutorange.github.io/js-elbe/).\n\nThe entire public API is expressed in terms of (typescript) interfaces, these\nare fully documented.\n\nThe docs can be viewed offline from the directory `docs`. Tests with more examples are in `test`. \n\n```javascript\nconst lib = require(\"elbe\");\n```\n\nThis returns an object with the following entries:\n\n* [Collectors](https://blutorange.github.io/js-elbe/globals.html#collectors)\n* [InplaceStreamFactory](https://blutorange.github.io/js-elbe/globals.html#inplacestreamfactory)\n* [Methods](https://blutorange.github.io/js-elbe/globals.html#appendCause)\n* [monkeyPatch](https://blutorange.github.io/js-elbe/globals.html#monkeypatch)\n* [stream](https://blutorange.github.io/js-elbe/globals.html#stream)\n* [TryFactory](https://blutorange.github.io/js-elbe/globals.html#tryfactory)\n* [TypesafeStreamFactory](https://blutorange.github.io/js-elbe/globals.html#typesafestreamfactory)\n\nThe [IStream](https://blutorange.github.io/js-elbe/interfaces/istream.html) contains all the juicy methods you want. A stream is created by an [InplaceStreamFactory](https://blutorange.github.io/js-elbe/globals.html#inplacestreamfactory), accessible via `require(\"elbe\").factory`. Read\nbelow for further details.\n\n# Install\n\nYou know the drill.\n\n```sh\nnpm install --save elbe\n```\n\nThen load it\n\n```javascript\nconst { stream, factory } = require(\"elbe\");\n// or\nimport { stream, factory } from \"elbe\";\n```\n\nOr use the standalone in `dist/elbe.js` that includes all required npm libraries and\nwas transformed with babel. Within a browser it registers globally as `window.Elbe`.\n\n# Usage\n\nYou can create a stream either from an iterable source such as an array\nor a `Set`; or use one of the factory methods such as `times` or `random`.\nThe created stream then provides several methods such as `map` or `collect`\nto operate on its items.\n\nGenerate a stream of 100 numbers between 1 and 100 and sums them, as fast as Gauss.\n\n```javascript\nconst factory = require(\"elbe\").factory;\nfactory.times(100,1,100).sum()\n// =\u003e 5050\n```\n\nGenerate numbers between 1.4 and 1.5, and take the one whose square is closest to 2.\n\n```javascript\nconst factory = require(\"elbe\").factory;\nfactory.times(1000,1.4,1.5).minBy(x =\u003e Math.abs(x * x - 2))\n// =\u003e 1.41421...\n```\n\nGenerate a stream from an array.\n\n```javascript\nconst { stream } = require(\"elbe\");\nstream([1,2,3]).map(...).filter(...).limit(1).group(...);\n```\n\nThe following entries exist on the object when requiring the library:\n\n```javascript\nconst lib = require(\"elbe\");\nlib = {\n    stream, // shortcut for InplaceStreamFactory.stream\n    factory, // shortcut for InplaceStreamFactory\n    monkeyPatch, // function that patches some Object prototypes\n    InplaceStreamFactory: { // see interface 'StreamFactory'\n      stream,\n      times,\n      generate,\n      ...\n    }\n    TypesafeStreamFactory: { // see interface 'StreamFactory'\n      from,\n      times,\n      generate,\n      ...\n    },\n    TryFactory, // see interface 'ITryFactory'\n    Collectors: { // see interface 'ICollectors'\n        join,\n        group,\n        ...\n    },\n    Methods: { // contains all methods documented in 'Methods'\n        filter, \n        group,\n        map,\n        ...\n    }\n}\n```\n\nThere are three different ways of using the stream methods:\n\n## Standalone functions\n\nAll methods are available as stand-alone functions taking an\niterable as their first argument.\n\n```javascript\nconst { Collectors, Methods: {map, filter, collect} } = require(\"elbe\");\n\nconst iterable = [1,2,3];\n\nmap(iterable, x =\u003e 2*x); // =\u003e Iterable[2,4,6]\n\nfilter(iterable, x =\u003e x \u003e 2); // =\u003e Iterable[4,6]\n\ncollect(iterable, Collectors.join()); // =\u003e \"4,6\"\n```\n\nAll factory methods for creating streams are also available:\n\n```javascript\nconst { Methods: {times} } = require(\"elbe\");\ntimes(10).map(i =\u003e i + 1).toArray()\n// =\u003e [1,2,3,4,5,6,7,8,9,10]\n```\n\n## Stream wrapper\n\nFor easier chaining, there are also two wrapper classes\navailable for the stand-alone functions.\n\nThe inplace stream comes with less overhead, but is not typesafe.\nThis is most likely irrelevant unless you are using TypeScript.\n\n```javascript\nconst { stream } = require(\"elbe\");\n\nstream([1,2,3]).map(x =\u003e 2*x).filter(x =\u003e x \u003e 2).concat([7,9]).join(\",\");\n// =\u003e \"4,6,7,9\"\n```\n\nThe typesafe streams creates a new wrapper instance when\nchaining for type safety. The overhead should not be large.\n\n```javascript\nconst stream = require(\"elbe\").TypesafeStreamFactory.stream;\n\nstream([1,2,3]).map(x =\u003e 2*x).filter(x=\u003ex\u003e2).concat([7,9]).join(\",\");\n// =\u003e \"4,6,7,9\"\n```\n\nOnce a stream was chained (consumed), it must not be used anymore,\nor an error is thrown:\n\n```javascript\nconst stream = require(\"elbe\").TypesafeStreamFactory.stream;\n\nconst s = stream([1,2,3]);\n\ns.map(x =\u003e x * x); // =\u003e Stream[1,4,9]\n\n// Error: \"Stream was already consumed.\"\ns.filter(x =\u003e x \u003e 2);\n```\n\nSimilarly for inplace streams: \n\n```javascript\nconst stream = require(\"elbe\").InplaceStreamFactory.stream;\n\nconst s = stream([1,2,3]);\n\ns.map(x =\u003e x * x); // =\u003e Stream[2,4,6]\n\ns.filter(x =\u003e x \u003e 2); // =\u003e Stream[4,6]\n\ns.join() // =\u003e \"46\"\n\ns.join() // Error: \"Stream was already consumed.\"\n```\n\n### Unlimited streams\n\nStream can be of unlimited (`infinite`) length, one common example are generators\nsuch as random number generators:\n\n```javascript\nconst { InplaceStreamFactory: factory } = require(\"elbe\");\nconst unlimitedStream = factory.generate(Math.random);\n```\n\nMethods operating on streams try to read only as many items from the stream\nas required. This means you can create chains of stream operations on unlimited\nstreams and not have it hang, as long as the terminal operation does not request\nall items. For example:\n\n```javascript\n// Returns the first item\nfactory.generate(Math.random)\n  .map(x=\u003e10*x)\n  .filter(x=\u003ex\u003e5)\n  .first();\n\n// Returns the first 20 items.\nfactory.generate(Math.random)\n  .map(x=\u003e10*x)\n  .filter(x=\u003ex\u003e5)\n  .limit(20)\n  .toArray()\n\n// Returns the first 20 items and leaves the stream open so\n// that you can read more items from it later.\nfactory.generate(Math.random)\n  .map(x=\u003e10*x)\n  .filter(x=\u003ex\u003e5)\n  .splice(20)\n```\n\nA notable example that always needs to read the entire stream is `IStream#reverse`.\nFiltering the stream for uniqueness with `IStream#unique` and `IStream#uniqueBy`\nsupports unlimited stream.s\n\n### Note for typescript users\n\nSome methods from `IStream` have the special return type `this`. They DO NOT\nreturn the same object; but rather `this` is used to indicate that the returned\nstream is of the same type as the stream on which the method was called. This allows\nthe typescript compiler to infer that a subclass of `IStream` remains as such even\nwhen calling methods from the super type. To illustrate:\n\n```javascript\n// s is now of type IStream\u003cnumber\u003e\ns = stream([1,2,3])\n\n// s is now a ITryStream\u003cnumber\u003e with some additional methods\ns = s.try(/*something dangerous*/)\n\n// `limit` is a method from IStream\u003cT\u003e, and without the `this` return\n// type, s would be downgraded to a normal stream, losing all additional\n// methods from ITryStream\u003cT\u003e.\n//\n// s is still of type ITryStream\u003cnumber\u003e, but may be a different\n// instance than before. For this to work with the typescript compile,\n// the return type is required to be `this`.\ns = s.limit(2)\n```\n\n## Monkey patching\n\nI would not recommend it, but you can monkey-patch a `stream` method to some objects.\nMay be helpful for testing or prototyping.\n\n```javascript\nrequire(\"elbe\").monkeypatch();\n\n[1,2,3].stream().map(x =\u003e x + 4).toSet();\n// =\u003e Set[5,6,7]\n\n\"foobar\".stream().filter(x =\u003e x \u003e \"d\").toArray();\n// =\u003e [\"f\", \"o\", \"o\", \"r\"]\n\nnew Set([1,2,3]).stream();\n// =\u003e Stream[1,2,3]\n\nnew Map([\"foo\", 3], [\"bar\", 9]).stream();\n// =\u003e Stream[ [\"foo\", 3], [\"bar\", 9] ]\n\n({foo: 3, bar: 9}).stream();\n// =\u003e Stream[ {key: \"foo\", value: 3}], {key: \"bar\", value: 9} ]\n```\n\n# Catching errors\n\nUse the `try` method to handle errors during stream operations.\n\n```javascript\nconst { stream, TryFactory } = require(\"elbe\");\nstream([json1, json2, json3]).try(JSON.parse);\n\n// The same effect could be achieved by mapping each item manually\nstream([json1, json2, json3]).map(x =\u003e lib.TryFactory.of(() =\u003e JSON.parse(x)))\n```\n\nThis returns a stream with `Try` objects encapsulating the error, if one occured.\n\nTo get the values of the successful operations:\n\n```javascript\n// Logs errors to the console, removes them from the stream, and\n// returns successfully parsed JSON objects.\nstream([json1, json2, json3]).try(JSON.parse).discardError().toArray()\n```\n\nTo get the values of the successful and failed operations:\n\n```javascript\nconst result = stream([json1, json2, json3]).try(JSON.parse).partition(x =\u003e x.success);\n// do something with the errors\nresult.false.forEach(errorTry =\u003e { ... })\n// do something with the succesful values\nresult.true.forEach(valueTry =\u003e { ... })\n\n// Alternatively, call an error/success handler\nconst s = stream([json1, json2, json3]).try(JSON.parse)\ns.then(json =\u003e {/* success handler*/}, error =\u003e {/*error handler*/})\n```\n\nTo provide a default for failed operations:\n\n```javascript\nstream(json1, json2, json3).try(JSON.parse).orElse(undefined);\n// JSON object or undefined.\n```\n\n# Changelog\n\n[See the changelog.](https://github.com/blutorange/js-elbe/blob/master/CHANGELOG.md)\n\n# Build\n\nMake sure you fetch all dependencies\n\n```sh\nnpm install\n```\n\nThen run\n\n```sh\nnpm run build\n```\n\nThis may fail on Windows, who but a rabbit knows...\n\n# Teh name\n\nMany a barrel of water streams, but never rolls, down the [Elbe river](https://en.wikipedia.org/wiki/Elbe).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblutorange%2Fjs-elbe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fblutorange%2Fjs-elbe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblutorange%2Fjs-elbe/lists"}