{"id":43011641,"url":"https://github.com/eswann/bootsy","last_synced_at":"2026-01-31T05:34:49.374Z","repository":{"id":57190161,"uuid":"351298697","full_name":"eswann/bootsy","owner":"eswann","description":"An easy to use functional-lite node library to make all the things easier!","archived":false,"fork":false,"pushed_at":"2021-09-11T06:22:00.000Z","size":316,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-21T00:20:52.070Z","etag":null,"topics":["bootsy","compose","lambda","pipe","ramda"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eswann.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-03-25T03:35:32.000Z","updated_at":"2022-02-02T03:40:28.000Z","dependencies_parsed_at":"2022-08-27T10:41:37.365Z","dependency_job_id":null,"html_url":"https://github.com/eswann/bootsy","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/eswann/bootsy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eswann%2Fbootsy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eswann%2Fbootsy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eswann%2Fbootsy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eswann%2Fbootsy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eswann","download_url":"https://codeload.github.com/eswann/bootsy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eswann%2Fbootsy/sbom","scorecard":{"id":383474,"data":{"date":"2025-08-11","repo":{"name":"github.com/eswann/bootsy","commit":"e26cbb050e0915791db81ba3b429ef8453b63c56"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.5,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 0/26 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/ci-build.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci-build.yml:10: update your workflow using https://app.stepsecurity.io/secureworkflow/eswann/bootsy/ci-build.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci-build.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/eswann/bootsy/ci-build.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci-build.yml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/eswann/bootsy/ci-build.yml/main?enable=pin","Warn: npmCommand not pinned by hash: .github/workflows/ci-build.yml:17","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned","Info:   0 out of   1 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":0,"reason":"19 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-w8qv-6jwh-64r5","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-4q6p-r6v2-jvc5","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-qrpm-p2h7-hrv2","Warn: Project is vulnerable to: GHSA-mwcw-c2x4-8c55","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-9wv6-86v2-598j","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-18T16:01:08.651Z","repository_id":57190161,"created_at":"2025-08-18T16:01:08.651Z","updated_at":"2025-08-18T16:01:08.651Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28930417,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-31T04:05:25.756Z","status":"ssl_error","status_checked_at":"2026-01-31T04:02:35.005Z","response_time":128,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["bootsy","compose","lambda","pipe","ramda"],"created_at":"2026-01-31T05:34:49.305Z","updated_at":"2026-01-31T05:34:49.366Z","avatar_url":"https://github.com/eswann.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Bootsy\n\n\u003cimg src=\"https://lastfm.freetls.fastly.net/i/u/ar0/cedc846292324665b46d380e72b16469.jpg\"\u003e\u003c/img\u003e\n\nFunky fresh (and easy) functional-lite!\n\n\u003cimg src=\"https://github.com/eswann/bootsy/actions/workflows/ci-build.yml/badge.svg\"\u003e\u003c/img\u003e\n[![Coverage Status](https://coveralls.io/repos/github/eswann/bootsy/badge.svg?branch=main)](https://coveralls.io/github/eswann/bootsy?branch=main)\n\n### Why? \nBootsy was created to make functional composition as pragmatic as possible. Sure there's Ramda, Lodash, and others,\nbut the idea behind Bootsy was to make everyday functional tasks easier, and provide additional features\nthat we've found helpful in enterprise production apps.\n\nIn addition, side effects are discouraged in pure functional programming, but in the real world of enterprise development,\nside effects are necessary to perform async operations such as saving data etc...\nWe encourage piping and composition as a way to organize code, even code that necessarily has side effects.\n\n## Features\n...that set us apart\n* [Typescript gives us excellent intellisense and type checking](#typescript)\n* [Async pipe/composition](#async-support)\n* [Built-in logging](#built-in-logging-and-timings)\n* [Built-in timing for each piped function](#built-in-logging-and-timings)\n* [Auto-merge of input and output arguments](#auto-merge)\n* [Curry argument detection with \"Curry Merge\"](#curry)\n* Comprehensive test coverage\n\n### Typescript\nTypescript combined with JSDoc gives us great editor support!\n\u003cimg src=\"https://github.com/eswann/bootsy/blob/main/readme-assets/editor-support-1.png?raw=true\"\u003e\u003c/img\u003e\n\n### Async Support\nFor pragmatic functional-lite, the ability to pipe async methods is essential, for this reason we\nsupport pipeAsync and composeAsync\n\n```javascript\nconst testAsyncFunc1 = async (myArg) =\u003e {\n  const result1 = await myAsyncFunc(myArg)\n  const result2 = await myAsyncFunc2(result1)\n  return result2 \n}\nconst testAsyncFunc2 = async (myArg) =\u003e {\n  const result3 = await myAsyncFunc3(myArg)\n  return `${rootText} One nation under a groove`\n}\n// Pipe it!!! \nconst pipedResult = await pipeAsync(testAsyncFunc1, testAsyncFunc2)('Give up the funk!')\n// Compose it!!!\nconst composedResult = await composeAsync(testAsyncFunc2, testAsyncFunc1)('funk the up Give!')\n```\n\n### Built-in Logging and Timings\nBy default, Bootsy always logs errors encountered while running each function (and then rethrows the error).\nLogging is set to **info** level by default, but this can be adjusted using configuration setup\nat a global or function level, and you can provide your logger of choice! The default is the console.\n\nBootsy will log each function call with timings at the **debug** level or by explicitly setting the **logTimings**\nflag in the configuration options.\n\n##### Options (including logging) can be set up globally\n```javascript\nconst myCustomLogger = {\n  trace: function (message, optionalParams) {},\n  debug: function (message, optionalParams) {},\n  info: function (message, optionalParams) {},\n  warn: function (message, optionalParams) {},\n  error: function (message, optionalParams) {},\n}\n// Global logging setup, both arguments are optional\nConfig.initialize({logLevel: Loglevel.debug, logger: myCustomLogger})\n```\n\n##### Options (including logging) can be set up per composed functions call (pipe/compose/etc...)\nThe first argument of one of our pipe/compose calls can be options to override global options. The provided\noptions will be merged into the global options for the specific call only.\n```javascript\n// Alternately any call to one of our composition functions can accept options as the first argument\nconst myCustomLogger = {\n  trace: function (message, optionalParams) {},\n  debug: function (message, optionalParams) {},\n  info: function (message, optionalParams) {},\n  warn: function (message, optionalParams) {},\n  error: function (message, optionalParams) {},\n}\nconst options = {\n  logLevel: Loglevel.error,\n  logger: myCustomLogger\n}\n// Pipe with explicit options provided\nconst pipedResult = await pipeAsync(options,  testAsyncFunc1, testAsyncFunc2)('Give up the funk!')\n```\n\n##### Turn on timings explicitly\nThe first argument of one of our pipe/compose calls can be options to override global options. The provided\noptions will be merged into the global options for the specific call only.\n```javascript\n// Global setup of logging timings\nConfig.initialize({logTimings:true})\n// Or pass them into any pipe/compose/etc...\nconst options = {\n  logTimings: true\n}\n// Pipe with explicit options provided\nconst pipedResult = await pipeAsync(options,  testAsyncFunc1, testAsyncFunc2)('Bootzilla')\n```\n\n### Auto-merge\nSo what is auto-merge? It's actually my favorite feature of Bootsy, but it's a little hard to explain.\nIn JavaScript, it's very common to pass all function parameters in a single object argument, as in the example below.\nNotice that text1 and text2 below are actually properties of one argument, not separate arguments. This pattern is\nwell-supported by editors/JSDoc etc...\nThe following article explains why we would want to follow this pattern in JavaScript:\nhttps://levelup.gitconnected.com/always-pass-one-argument-to-your-javascript-function-4140d909937e\n\n```javascript\nconst someFunc = ({ text1, text2 }) =\u003e {\n  const text3 = 'random text to append'\n  return { text: `${text1} ${text2} ${text3}` }\n}\n```\n\nWhen piping functions like these, we often find ourselves passing unused arguments into functions\nso that they can be conveyed to another function further down the pipe.\nYes there are ways around this, but our goal is to make piping functions both clean and easy.\n```javascript\nconst func1 = ({ text1, text2 }) =\u003e {\n  const text3 = text1 + text2\n  return {text1, text3}\n}\n// ---\u003e *** The problem ***\n// This function doesn't do anything with text3, but func3 needs it!\n// So I have to pass it through, but this is just bad in so many ways!!!\nconst func2 = ({text1, text3}) =\u003e {\n  const text4 = doSomthing(text1)\n  return {text3, text4}\n}\nconst func3 = ({text3, text4}) =\u003e {\n  const text5 = doAnotherThing(text3, text4)\n  return text5\n}\nconst pipedResult = await pipe(func1,  func2,  func3)({text1: 'Aqua', text2: 'Boogie'})\n```\n\nWith auto-merge, which is enabled by default, you don't have to do this.\nEach function can focus on its responsibilities only.\n```javascript\nconst func1 = ({ text1, text2 }) =\u003e {\n  const text3 = text1 + text2\n  return {text1, text3}\n}\n// I no longer have to pass text3 through\nconst func2 = ({text1}) =\u003e {\n  const text4 = doSomthing(text1)\n  return {text4}\n}\n// Automerge makes sure that text3 is supplied to func3\nconst func3 = ({text3, text4}) =\u003e {\n  const text5 = doAnotherThing(text3, text4)\n  return text5\n}\nconst pipedResult = await pipe(func1,  func2,  func3)({text1: 'Soul', text2: 'Power'})\n```\n\n#### Configuring Auto-merge\n```javascript\n// Global setup of autoMerge\nConfig.initialize({autoMerge: false})\n// Or pass them into any pipe/compose/etc...\nconst options = {\n  autoMerge: false\n}\n// Pipe with explicit options provided\nconst pipedResult = await pipeAsync(options,  func1, func2)({text1: 'Super', text2: 'Bad'})\n```\n\n### Composition functions\nComposition functions are demonstrated in the examples above, these are:\nPipe, PipeAsync, Compose, and ComposeAsync.\n\nPipe executes the provided functions from left to right, Compose from right to left.\nThe results of each function are passed to the next, see [Auto-merge](#auto-merge) for more information\non automatic parameter inference in more complex composition scenarios.\n```javascript\nconst testFunc1 = (myArg) =\u003e {\n  return 'Cool ' + myArg\n}\nconst testFunc2 = async (myArg) =\u003e {\n  return `${myArg}: One nation under a groove`\n}\n// Pipe it!!!\nconst pipedResult = await pipe(testFunc1, testFunc2)('Give up the funk!')\n// Compose it!!!\nconst composedResult = await compose(testFunc2, testFunc1)('funk the up Give!')\n```\n\n### Other Functions\nThe following functions are included to help with functionality needed in common functional scenarios, but combine us\nwith any functional library out there! We play just fine with Ramda, Lodash, etc...\n\n#### Curry\nBootsy has the tastiest curry! Why?\nCurry automatically detects and performs a [Curry Merge](#curry-merge) if the function matches the Curry Merge pattern,\nso you can just curry all the things!\n\nCurried functions return a new function until all expected\narguments are provided. In our case, \"curry\" and \"partial\" are the same, meaning curry can also accept initial parameters\nas part of the call to curry.\n```javascript\n  const addFourNumbers = (a, b, c, d) =\u003e a + b + c + d\n  // Create a curried version of any function, include some arguments or none\n  const curriedFunction = curry(addFourNumbers, 1)\n  // Calling is with some additional arguments returns a new partially fulfilled function\n  const partiallyFulfilledCurriedFunction = curriedFunction(2, 3)\n  // Calling it at any time with the remaining arguments causes the function to evaluate\n  const result = partiallyFulfilledCurriedFunction(4)\n\n  // Global curry merge detection, default is true\n  Config.initialize({curryMerge: true})\n```\n\n#### Curry Merge\nWorks like curry, but in the case where a single object argument is provided.\n```javascript\n  const addThreeNumbers = ({ a, b, c }) =\u003e a + b + c\n  // CurryMerge with one parameter provided will return a partially fulfilled function\n  const curriedFunction = curryMerge(addThreeNumbers, { a: 1 })\n  // Calling it at any time with the remaining arguments causes the function to evaluate\n  const result = curriedFunction({b:2, c:3})\n```\n\n#### Map Async\nThis is like the standard JavaScript map, but async, and with our top level logging, timing, etc...\nMap Async applies every argument provided to a single function independently and returns an awaited array of results:\nOne result for each evaluation of the function.\n```javascript\n  const [result1, result2, result3] = await mapAsync(makeYourOwnRhyme, ['skittle', 'diddle', 'fiddle'])\n```\n\n#### Over Async\nOver Async can be though of as the inverse of Map Async.\nOver Async applies the same args to an array of functions and awaits all of them, returning an array of results.\n```javascript\n  const [result1, result2, result3] = await overAsync(testAsyncFunc1, testAsyncFunc2, testAsyncFunc3)('Bootsy', 'Catfish')\n```\n\n#### If there is a support function that you believe should come out-of-the-box, shoot us a line and let us know!\n\n### More Examples are available in the tests!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feswann%2Fbootsy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feswann%2Fbootsy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feswann%2Fbootsy/lists"}